clementine-agent 1.18.93 → 1.18.96

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.
@@ -14636,6 +14636,103 @@ if('serviceWorker' in navigator){navigator.serviceWorker.getRegistrations().then
14636
14636
  font-weight: 600;
14637
14637
  }
14638
14638
 
14639
+ /* ── Agent edit modal — tabs + Equipment panel ─────────────────── */
14640
+ .agent-tab {
14641
+ background: transparent;
14642
+ border: 0;
14643
+ border-bottom: 2px solid transparent;
14644
+ color: var(--text-muted);
14645
+ cursor: pointer;
14646
+ font-size: 13px;
14647
+ padding: 8px 14px;
14648
+ margin-bottom: -1px;
14649
+ }
14650
+ .agent-tab:hover { color: var(--text-primary); }
14651
+ .agent-tab.active {
14652
+ color: var(--text-primary);
14653
+ border-bottom-color: var(--clementine);
14654
+ font-weight: 600;
14655
+ }
14656
+ .agent-tools-filter {
14657
+ background: transparent;
14658
+ border: 1px solid var(--border);
14659
+ border-radius: 4px;
14660
+ color: var(--text-muted);
14661
+ cursor: pointer;
14662
+ font-size: 11px;
14663
+ padding: 4px 8px;
14664
+ }
14665
+ .agent-tools-filter:hover { color: var(--text-primary); }
14666
+ .agent-tools-filter.active {
14667
+ background: var(--clementine);
14668
+ border-color: var(--clementine);
14669
+ color: #000;
14670
+ font-weight: 600;
14671
+ }
14672
+ .agent-tool-row {
14673
+ align-items: center;
14674
+ color: var(--text-primary);
14675
+ cursor: pointer;
14676
+ display: flex;
14677
+ font-size: 12px;
14678
+ gap: 6px;
14679
+ line-height: 1.4;
14680
+ padding: 4px 6px;
14681
+ border-radius: 4px;
14682
+ }
14683
+ .agent-tool-row:hover { background: rgba(255,255,255,0.03); }
14684
+ .agent-tool-row .tt-name { flex: 1; min-width: 0; }
14685
+ .agent-tool-row .tt-badge {
14686
+ background: rgba(255,255,255,0.06);
14687
+ border-radius: 3px;
14688
+ color: var(--text-muted);
14689
+ font-size: 9px;
14690
+ font-weight: 700;
14691
+ letter-spacing: 0.4px;
14692
+ padding: 1px 5px;
14693
+ text-transform: uppercase;
14694
+ }
14695
+ .agent-tool-row .tt-badge.cli { background: rgba(120, 200, 255, 0.12); color: #79c8ff; }
14696
+ .agent-tool-row .tt-badge.sdk { background: rgba(168, 200, 255, 0.12); color: #a8c8ff; }
14697
+ .agent-tool-row .tt-badge.mcp { background: rgba(180, 130, 255, 0.12); color: #c8a8ff; }
14698
+ .agent-tool-row .tt-badge.api { background: rgba(255, 200, 120, 0.12); color: #ffc878; }
14699
+ .agent-tool-row .tt-badge.composio { background: rgba(255, 138, 138, 0.14); color: #ff8a8a; }
14700
+ .agent-tool-row .tt-badge.project, .agent-tool-row .tt-badge['project-mcp'] { background: rgba(120, 255, 180, 0.12); color: #78ffb4; }
14701
+ .agent-tool-row .tt-status {
14702
+ align-items: center;
14703
+ color: var(--text-muted);
14704
+ display: inline-flex;
14705
+ font-size: 10px;
14706
+ gap: 3px;
14707
+ }
14708
+ .agent-tool-row .tt-status .tt-dot {
14709
+ border-radius: 50%;
14710
+ height: 7px;
14711
+ width: 7px;
14712
+ }
14713
+ .agent-tool-row .tt-status.ready .tt-dot { background: var(--green); }
14714
+ .agent-tool-row .tt-status.needs-setup .tt-dot { background: #f59e0b; }
14715
+ .agent-tool-row .tt-status.not-installed .tt-dot { background: #888; }
14716
+ .agent-tool-row .tt-status.blocked .tt-dot { background: #ef4444; }
14717
+ .agent-tool-cat {
14718
+ color: var(--text-muted);
14719
+ cursor: pointer;
14720
+ font-size: 11px;
14721
+ font-weight: 700;
14722
+ letter-spacing: 0.5px;
14723
+ margin: 10px 0 4px;
14724
+ text-transform: uppercase;
14725
+ user-select: none;
14726
+ }
14727
+ .agent-tool-cat .cat-count {
14728
+ color: var(--text-muted);
14729
+ font-weight: 500;
14730
+ margin-left: 6px;
14731
+ text-transform: none;
14732
+ letter-spacing: 0;
14733
+ }
14734
+ .agent-tool-row.hidden, .agent-tool-cat.hidden { display: none; }
14735
+
14639
14736
  /* ── Office Hero — Clementine ─────────── */
14640
14737
  .office-hero {
14641
14738
  background: var(--bg-card);
@@ -19984,9 +20081,13 @@ if('serviceWorker' in navigator){navigator.serviceWorker.getRegistrations().then
19984
20081
  </div>
19985
20082
  </div>
19986
20083
 
19987
- <!-- Agent Create/Edit Modal -->
20084
+ <!-- Agent Create/Edit Modal — tabbed editor reused for hired agents
20085
+ AND for Clementine herself (see editClementine() in dashboard JS).
20086
+ Element IDs are stable so existing populator/submit code keeps
20087
+ working; new tabs (Equipment, Goals) add fields without breaking the
20088
+ old contract. -->
19988
20089
  <div id="agent-modal" class="modal-overlay">
19989
- <div class="modal" style="width:520px">
20090
+ <div class="modal" style="width:760px;max-width:95vw">
19990
20091
  <div class="modal-header">
19991
20092
  <h3 id="agent-modal-title">Hire a New Team Member</h3>
19992
20093
  <button class="btn-ghost btn-sm" onclick="hideAgentModal()">&times;</button>
@@ -19994,26 +20095,82 @@ if('serviceWorker' in navigator){navigator.serviceWorker.getRegistrations().then
19994
20095
  <div class="modal-body">
19995
20096
  <form id="agent-form" onsubmit="submitAgentForm(event)">
19996
20097
  <input type="hidden" id="agent-edit-slug" value="">
19997
- <div style="margin-bottom:12px">
19998
- <label style="display:block;color:var(--text-muted);font-size:12px;margin-bottom:4px">Name *</label>
19999
- <input id="agent-name" required style="width:100%;padding:8px;background:var(--bg-input);border:1px solid var(--border);border-radius:6px;color:var(--text-primary)" placeholder="Research Agent">
20000
- </div>
20001
- <div style="margin-bottom:12px">
20002
- <label style="display:block;color:var(--text-muted);font-size:12px;margin-bottom:4px">Role Description *</label>
20003
- <input id="agent-description" required style="width:100%;padding:8px;background:var(--bg-input);border:1px solid var(--border);border-radius:6px;color:var(--text-primary)" placeholder="Deep-dive research and analysis">
20098
+ <input type="hidden" id="agent-edit-mode" value="agent"><!-- 'agent' or 'clementine' -->
20099
+
20100
+ <!-- Tab strip ---------------------------------------------------- -->
20101
+ <div id="agent-modal-tabs" style="display:flex;gap:2px;border-bottom:1px solid var(--border);margin-bottom:14px">
20102
+ <button type="button" class="agent-tab active" data-tab="identity" onclick="switchAgentTab('identity')">Identity</button>
20103
+ <button type="button" class="agent-tab" data-tab="equipment" onclick="switchAgentTab('equipment')">Equipment</button>
20104
+ <button type="button" class="agent-tab" data-tab="connections" onclick="switchAgentTab('connections')">Connections</button>
20105
+ <button type="button" class="agent-tab" data-tab="goals" onclick="switchAgentTab('goals')">Goals</button>
20106
+ <button type="button" class="agent-tab" data-tab="limits" onclick="switchAgentTab('limits')">Limits</button>
20004
20107
  </div>
20005
- <div style="margin-bottom:12px">
20006
- <label style="display:block;color:var(--text-muted);font-size:12px;margin-bottom:4px">Profile Photo URL</label>
20007
- <input id="agent-avatar-url" style="width:100%;padding:8px;background:var(--bg-input);border:1px solid var(--border);border-radius:6px;color:var(--text-primary)" placeholder="https://example.com/avatar.png">
20108
+
20109
+ <!-- Tab: Identity ------------------------------------------------ -->
20110
+ <div class="agent-tab-pane" data-tab-pane="identity">
20111
+ <div style="display:grid;grid-template-columns:1fr 1fr;gap:12px;margin-bottom:12px">
20112
+ <div>
20113
+ <label style="display:block;color:var(--text-muted);font-size:12px;margin-bottom:4px">Name *</label>
20114
+ <input id="agent-name" required style="width:100%;padding:8px;background:var(--bg-input);border:1px solid var(--border);border-radius:6px;color:var(--text-primary)" placeholder="Research Agent">
20115
+ </div>
20116
+ <div>
20117
+ <label style="display:block;color:var(--text-muted);font-size:12px;margin-bottom:4px">Model</label>
20118
+ <select id="agent-model" style="width:100%;padding:8px;background:var(--bg-input);border:1px solid var(--border);border-radius:6px;color:var(--text-primary)">
20119
+ <option value="">Default (Sonnet)</option>
20120
+ <option value="haiku">Haiku</option>
20121
+ <option value="sonnet">Sonnet</option>
20122
+ <option value="opus">Opus</option>
20123
+ </select>
20124
+ </div>
20125
+ </div>
20126
+ <div id="agent-identity-row-2" style="margin-bottom:12px">
20127
+ <label style="display:block;color:var(--text-muted);font-size:12px;margin-bottom:4px">Profile Photo URL</label>
20128
+ <input id="agent-avatar-url" style="width:100%;padding:8px;background:var(--bg-input);border:1px solid var(--border);border-radius:6px;color:var(--text-primary)" placeholder="https://example.com/avatar.png">
20129
+ </div>
20130
+ <div style="margin-bottom:12px">
20131
+ <label style="display:block;color:var(--text-muted);font-size:12px;margin-bottom:4px">Headline <span style="opacity:0.6">(short — appears on the agent card)</span> *</label>
20132
+ <input id="agent-description" required style="width:100%;padding:8px;background:var(--bg-input);border:1px solid var(--border);border-radius:6px;color:var(--text-primary)" placeholder="Deep-dive research and analysis">
20133
+ </div>
20134
+ <div style="margin-bottom:12px">
20135
+ <label style="display:block;color:var(--text-muted);font-size:12px;margin-bottom:4px">Mission &amp; persona <span style="opacity:0.6">(full system-prompt body — voice, expertise, working rules)</span></label>
20136
+ <textarea id="agent-personality" rows="8" style="width:100%;padding:8px;background:var(--bg-input);border:1px solid var(--border);border-radius:6px;color:var(--text-primary);resize:vertical;font-family:inherit" placeholder="You are a Research Agent specializing in..."></textarea>
20137
+ </div>
20138
+ <div id="agent-project-row" style="margin-bottom:12px">
20139
+ <label style="display:block;color:var(--text-muted);font-size:12px;margin-bottom:4px">Primary Project <span style="opacity:0.6">(optional — additional project access lives in the Equipment tab)</span></label>
20140
+ <select id="agent-project" style="width:100%;padding:8px;background:var(--bg-input);border:1px solid var(--border);border-radius:6px;color:var(--text-primary)">
20141
+ <option value="">None</option>
20142
+ </select>
20143
+ </div>
20008
20144
  </div>
20009
- <div style="margin-bottom:12px">
20010
- <label style="display:block;color:var(--text-muted);font-size:12px;margin-bottom:4px">Onboarding Brief</label>
20011
- <textarea id="agent-personality" rows="4" style="width:100%;padding:8px;background:var(--bg-input);border:1px solid var(--border);border-radius:6px;color:var(--text-primary);resize:vertical" placeholder="You are a Research Agent specializing in..."></textarea>
20145
+
20146
+ <!-- Tab: Equipment ----------------------------------------------- -->
20147
+ <div class="agent-tab-pane" data-tab-pane="equipment" style="display:none">
20148
+ <div style="display:flex;gap:8px;align-items:center;margin-bottom:8px;flex-wrap:wrap">
20149
+ <input id="agent-tools-search" type="search" placeholder="Search tools, CLIs, MCP, projects, integrations..." oninput="filterAgentTools()" style="flex:1;min-width:240px;padding:7px 10px;background:var(--bg-input);border:1px solid var(--border);border-radius:6px;color:var(--text-primary);font-size:12px">
20150
+ <div id="agent-tools-filters" style="display:flex;gap:4px">
20151
+ <button type="button" class="agent-tools-filter active" data-filter="all" onclick="setAgentToolsFilter('all')">All</button>
20152
+ <button type="button" class="agent-tools-filter" data-filter="enabled" onclick="setAgentToolsFilter('enabled')">Enabled</button>
20153
+ <button type="button" class="agent-tools-filter" data-filter="ready" onclick="setAgentToolsFilter('ready')">Ready</button>
20154
+ <button type="button" class="agent-tools-filter" data-filter="needs-setup" onclick="setAgentToolsFilter('needs-setup')">Needs setup</button>
20155
+ </div>
20156
+ </div>
20157
+ <div id="agent-tools-summary" style="font-size:11px;color:var(--text-muted);margin-bottom:6px">Loading…</div>
20158
+ <div id="agent-tools-panel" style="max-height:380px;overflow-y:auto;border:1px solid var(--border);border-radius:6px;padding:8px;background:var(--bg-input)">
20159
+ <div style="color:var(--text-muted);font-size:12px">Loading tools...</div>
20160
+ </div>
20161
+ <div style="margin-top:8px;font-size:11px;color:var(--text-muted);line-height:1.5">
20162
+ <span style="display:inline-block;width:8px;height:8px;border-radius:50%;background:var(--green);vertical-align:middle;margin-right:4px"></span>Ready &nbsp;
20163
+ <span style="display:inline-block;width:8px;height:8px;border-radius:50%;background:#f59e0b;vertical-align:middle;margin-right:4px"></span>Needs setup &nbsp;
20164
+ <span style="display:inline-block;width:8px;height:8px;border-radius:50%;background:#888;vertical-align:middle;margin-right:4px"></span>Not installed &nbsp;
20165
+ <span style="display:inline-block;width:8px;height:8px;border-radius:50%;background:#ef4444;vertical-align:middle;margin-right:4px"></span>Blocked
20166
+ </div>
20012
20167
  </div>
20013
- <div style="display:grid;grid-template-columns:1fr 1fr;gap:12px;margin-bottom:12px">
20014
- <div>
20015
- <label style="display:block;color:var(--text-muted);font-size:12px;margin-bottom:4px">Channel</label>
20016
- <div id="agent-channel-list" style="max-height:140px;overflow-y:auto;border:1px solid var(--border);border-radius:6px;padding:6px 8px;background:var(--bg-input)">
20168
+
20169
+ <!-- Tab: Connections -------------------------------------------- -->
20170
+ <div class="agent-tab-pane" data-tab-pane="connections" style="display:none">
20171
+ <div style="margin-bottom:14px">
20172
+ <label style="display:block;color:var(--text-muted);font-size:12px;margin-bottom:4px">Channels <span style="opacity:0.6">(Discord channels this agent will listen + post in)</span></label>
20173
+ <div id="agent-channel-list" style="max-height:160px;overflow-y:auto;border:1px solid var(--border);border-radius:6px;padding:6px 8px;background:var(--bg-input)">
20017
20174
  <div style="color:var(--text-muted);font-size:12px">Loading channels...</div>
20018
20175
  </div>
20019
20176
  <label style="display:flex;align-items:center;gap:6px;margin-top:6px;color:var(--text-muted);font-size:12px;cursor:pointer">
@@ -20025,76 +20182,15 @@ if('serviceWorker' in navigator){navigator.serviceWorker.getRegistrations().then
20025
20182
  Respond to all messages <span style="opacity:0.6">(not just @mentions)</span>
20026
20183
  </label>
20027
20184
  </div>
20028
- <div>
20029
- <label style="display:block;color:var(--text-muted);font-size:12px;margin-bottom:4px">Model</label>
20030
- <select id="agent-model" style="width:100%;padding:8px;background:var(--bg-input);border:1px solid var(--border);border-radius:6px;color:var(--text-primary)">
20031
- <option value="">Default (Sonnet)</option>
20032
- <option value="haiku">Haiku</option>
20033
- <option value="sonnet">Sonnet</option>
20034
- <option value="opus">Opus</option>
20035
- </select>
20036
- </div>
20037
- </div>
20038
- <div style="display:grid;grid-template-columns:1fr 1fr;gap:12px;margin-bottom:12px">
20039
- <div>
20040
- <label style="display:block;color:var(--text-muted);font-size:12px;margin-bottom:4px">Project Binding</label>
20041
- <select id="agent-project" style="width:100%;padding:8px;background:var(--bg-input);border:1px solid var(--border);border-radius:6px;color:var(--text-primary)">
20042
- <option value="">None</option>
20043
- </select>
20044
- </div>
20045
- <div>
20046
- <label style="display:block;color:var(--text-muted);font-size:12px;margin-bottom:4px">Security Clearance</label>
20047
- <select id="agent-tier" style="width:100%;padding:8px;background:var(--bg-input);border:1px solid var(--border);border-radius:6px;color:var(--text-primary)">
20048
- <option value="2">Tier 2 (Read/Write)</option>
20049
- <option value="1">Tier 1 (Read-only)</option>
20050
- </select>
20051
- </div>
20052
- <div>
20053
- <label style="display:block;color:var(--text-muted);font-size:12px;margin-bottom:4px">Monthly Budget <span style="opacity:0.6">(cents, 0 = unlimited)</span></label>
20054
- <input id="agent-budget" type="number" value="0" min="0" style="width:100%;padding:8px;background:var(--bg-input);border:1px solid var(--border);border-radius:6px;color:var(--text-primary)"
20055
- placeholder="e.g. 5000 = $50/month">
20056
- </div>
20057
- </div>
20058
- <details style="margin-bottom:12px;border:1px solid var(--border);border-radius:6px;padding:8px">
20059
- <summary style="cursor:pointer;color:var(--text-muted);font-size:12px;font-weight:600">Autonomous Sending Policy <span style="opacity:0.6">(for email-capable agents)</span></summary>
20060
- <div style="padding:8px 0 0;display:grid;grid-template-columns:1fr 1fr;gap:8px">
20061
- <div>
20062
- <label style="display:block;color:var(--text-muted);font-size:11px;margin-bottom:3px">Max Emails / Day</label>
20063
- <input id="agent-send-max-daily" type="number" value="50" min="0" max="500" style="width:100%;padding:6px;background:var(--bg-input);border:1px solid var(--border);border-radius:4px;color:var(--text-primary);font-size:12px">
20064
- </div>
20065
- <div>
20066
- <label style="display:block;color:var(--text-muted);font-size:11px;margin-bottom:3px">Approval Mode</label>
20067
- <select id="agent-send-approval" style="width:100%;padding:6px;background:var(--bg-input);border:1px solid var(--border);border-radius:4px;color:var(--text-primary);font-size:12px">
20068
- <option value="">Disabled (no autonomous sending)</option>
20069
- <option value="none">None (fully autonomous)</option>
20070
- <option value="first-in-sequence">First in sequence (approve first email per lead)</option>
20071
- <option value="all">All (approve every send)</option>
20072
- </select>
20073
- </div>
20074
- <div style="grid-column:span 2">
20075
- <label style="display:flex;align-items:center;gap:6px;color:var(--text-muted);font-size:12px;cursor:pointer">
20076
- <input id="agent-send-biz-hours" type="checkbox"> Restrict to business hours (8am–6pm)
20077
- </label>
20078
- </div>
20185
+ <div style="margin-bottom:14px">
20186
+ <label style="display:block;color:var(--text-muted);font-size:12px;margin-bottom:4px">Teammates this agent can message <span style="opacity:0.6">(comma-separated slugs)</span></label>
20187
+ <input id="agent-canmessage" style="width:100%;padding:8px;background:var(--bg-input);border:1px solid var(--border);border-radius:6px;color:var(--text-primary)" placeholder="analyst-agent, writer-agent">
20079
20188
  </div>
20080
- </details>
20081
- <div style="margin-bottom:12px">
20082
- <label style="display:block;color:var(--text-muted);font-size:12px;margin-bottom:4px">Team Connections (comma-separated slugs)</label>
20083
- <input id="agent-canmessage" style="width:100%;padding:8px;background:var(--bg-input);border:1px solid var(--border);border-radius:6px;color:var(--text-primary)" placeholder="analyst-agent, writer-agent">
20084
- </div>
20085
- <div style="margin-bottom:12px">
20086
- <label style="display:block;color:var(--text-muted);font-size:12px;margin-bottom:4px">Equipment & Access <span style="opacity:0.6">(click category to toggle all)</span></label>
20087
- <div id="agent-tools-panel" style="max-height:200px;overflow-y:auto;border:1px solid var(--border);border-radius:6px;padding:8px;background:var(--bg-input)">
20088
- <div style="color:var(--text-muted);font-size:12px">Loading tools...</div>
20189
+ <div style="margin-bottom:14px">
20190
+ <label style="display:block;color:var(--text-muted);font-size:12px;margin-bottom:4px">Allowed Users <span style="opacity:0.6">(Discord user IDs that may DM this agent — comma-separated)</span></label>
20191
+ <input id="agent-allowed-users" style="width:100%;padding:8px;background:var(--bg-input);border:1px solid var(--border);border-radius:6px;color:var(--text-primary)" placeholder="123456789012345678, 987654321098765432">
20089
20192
  </div>
20090
- </div>
20091
- <div style="margin-bottom:12px">
20092
- <label style="display:block;color:var(--text-muted);font-size:12px;margin-bottom:4px">Allowed Users <span style="opacity:0.6">(Discord user IDs, comma-separated)</span></label>
20093
- <input id="agent-allowed-users" style="width:100%;padding:8px;background:var(--bg-input);border:1px solid var(--border);border-radius:6px;color:var(--text-primary)" placeholder="123456789012345678, 987654321098765432">
20094
- </div>
20095
- <div style="margin-bottom:16px">
20096
- <div style="font-weight:600;font-size:13px;color:var(--text-primary);margin-bottom:8px;border-top:1px solid var(--border);padding-top:12px">Platform Connections</div>
20097
-
20193
+ <div style="margin-bottom:8px;font-weight:600;font-size:13px;color:var(--text-primary);border-top:1px solid var(--border);padding-top:12px">Platform Bots</div>
20098
20194
  <details id="discord-section" style="margin-bottom:10px">
20099
20195
  <summary style="cursor:pointer;color:var(--text-muted);font-size:12px;font-weight:600;margin-bottom:6px">Discord Bot <span id="discord-status-dot" style="display:none;width:8px;height:8px;border-radius:50%;display:inline-block;margin-left:4px"></span></summary>
20100
20196
  <div style="padding-left:8px">
@@ -20139,8 +20235,53 @@ if('serviceWorker' in navigator){navigator.serviceWorker.getRegistrations().then
20139
20235
  </div>
20140
20236
  </div>
20141
20237
  </details>
20238
+ </div><!-- /agent-tab-pane connections -->
20239
+
20240
+ <!-- Tab: Goals --------------------------------------------------- -->
20241
+ <div class="agent-tab-pane" data-tab-pane="goals" style="display:none">
20242
+ <div style="font-size:12px;color:var(--text-muted);margin-bottom:8px">Long-running objectives this agent owns. Reassigning here moves the goal&apos;s <code>owner</code> field — the existing Goals tab in Team picks the change up automatically.</div>
20243
+ <div id="agent-goals-panel" style="max-height:380px;overflow-y:auto;border:1px solid var(--border);border-radius:6px;padding:8px;background:var(--bg-input)">
20244
+ <div style="color:var(--text-muted);font-size:12px">Loading goals…</div>
20245
+ </div>
20142
20246
  </div>
20143
- <div style="display:flex;gap:8px;justify-content:flex-end">
20247
+
20248
+ <!-- Tab: Limits -------------------------------------------------- -->
20249
+ <div class="agent-tab-pane" data-tab-pane="limits" style="display:none">
20250
+ <div style="display:grid;grid-template-columns:1fr 1fr;gap:12px;margin-bottom:14px">
20251
+ <div id="agent-tier-row">
20252
+ <label style="display:block;color:var(--text-muted);font-size:12px;margin-bottom:4px">Security Clearance</label>
20253
+ <select id="agent-tier" style="width:100%;padding:8px;background:var(--bg-input);border:1px solid var(--border);border-radius:6px;color:var(--text-primary)">
20254
+ <option value="2">Tier 2 (Read/Write)</option>
20255
+ <option value="1">Tier 1 (Read-only)</option>
20256
+ </select>
20257
+ </div>
20258
+ <div>
20259
+ <label style="display:block;color:var(--text-muted);font-size:12px;margin-bottom:4px">Monthly Budget <span style="opacity:0.6">(cents, 0 = unlimited)</span></label>
20260
+ <input id="agent-budget" type="number" value="0" min="0" style="width:100%;padding:8px;background:var(--bg-input);border:1px solid var(--border);border-radius:6px;color:var(--text-primary)" placeholder="e.g. 5000 = $50/month">
20261
+ </div>
20262
+ </div>
20263
+ <div style="font-weight:600;font-size:13px;color:var(--text-primary);margin-bottom:8px;border-top:1px solid var(--border);padding-top:12px">Autonomous Sending Policy <span style="opacity:0.6;font-weight:400">(for email-capable agents)</span></div>
20264
+ <div style="display:grid;grid-template-columns:1fr 1fr;gap:8px;margin-bottom:8px">
20265
+ <div>
20266
+ <label style="display:block;color:var(--text-muted);font-size:11px;margin-bottom:3px">Max Emails / Day</label>
20267
+ <input id="agent-send-max-daily" type="number" value="50" min="0" max="500" style="width:100%;padding:6px;background:var(--bg-input);border:1px solid var(--border);border-radius:4px;color:var(--text-primary);font-size:12px">
20268
+ </div>
20269
+ <div>
20270
+ <label style="display:block;color:var(--text-muted);font-size:11px;margin-bottom:3px">Approval Mode</label>
20271
+ <select id="agent-send-approval" style="width:100%;padding:6px;background:var(--bg-input);border:1px solid var(--border);border-radius:4px;color:var(--text-primary);font-size:12px">
20272
+ <option value="">Disabled (no autonomous sending)</option>
20273
+ <option value="none">None (fully autonomous)</option>
20274
+ <option value="first-in-sequence">First in sequence (approve first email per lead)</option>
20275
+ <option value="all">All (approve every send)</option>
20276
+ </select>
20277
+ </div>
20278
+ </div>
20279
+ <label style="display:flex;align-items:center;gap:6px;color:var(--text-muted);font-size:12px;cursor:pointer">
20280
+ <input id="agent-send-biz-hours" type="checkbox"> Restrict to business hours (8am–6pm)
20281
+ </label>
20282
+ </div>
20283
+
20284
+ <div style="display:flex;gap:8px;justify-content:flex-end;margin-top:16px;border-top:1px solid var(--border);padding-top:12px">
20144
20285
  <button type="button" class="btn" onclick="hideAgentModal()">Cancel</button>
20145
20286
  <button type="submit" class="btn" style="background:var(--green);color:#000;font-weight:600" id="agent-submit-btn">Complete Hiring</button>
20146
20287
  </div>
@@ -24367,6 +24508,62 @@ async function refreshMiniDashboards() {
24367
24508
  failHtml += '</div>';
24368
24509
  }
24369
24510
 
24511
+ // ── Activity card (PRD §12 / 1.18.96) ──────────────────────────────
24512
+ // Fourth mini-dashboard card. Lite version of the PRD Agent Behavior
24513
+ // metrics — full per-tool / per-turn data needs Path B hooks, but
24514
+ // there is plenty to surface from CronRunEntry alone:
24515
+ // - runs/day average across the 7d window
24516
+ // - busiest task (highest run count)
24517
+ // - trigger breakdown (manual / scheduled / after / api / webhook)
24518
+ // The trigger bar is the most actionable signal — heavy "manual" =
24519
+ // automatable work; heavy "after" = chained workflows dragging cost.
24520
+ var runsPerDay = last7.length > 0 ? (last7.length / 7).toFixed(1) : '0';
24521
+ var taskCounts = {};
24522
+ for (var ti = 0; ti < last7.length; ti++) {
24523
+ var jn = last7[ti].jobName;
24524
+ if (!jn) continue;
24525
+ taskCounts[jn] = (taskCounts[jn] || 0) + 1;
24526
+ }
24527
+ var busiestTask = '—';
24528
+ var busiestCount = 0;
24529
+ Object.keys(taskCounts).forEach(function(name) {
24530
+ if (taskCounts[name] > busiestCount) { busiestTask = name; busiestCount = taskCounts[name]; }
24531
+ });
24532
+ var triggerOrder = ['manual', 'scheduled', 'after', 'api', 'webhook', 'other'];
24533
+ var triggerCounts = { manual: 0, scheduled: 0, after: 0, api: 0, webhook: 0, other: 0 };
24534
+ for (var tg = 0; tg < last7.length; tg++) {
24535
+ var trg = last7[tg].trigger;
24536
+ if (trg && triggerCounts[trg] !== undefined) triggerCounts[trg] += 1;
24537
+ else triggerCounts.other += 1;
24538
+ }
24539
+ var triggerColors = {
24540
+ manual: '#3b82f6',
24541
+ scheduled: '#10b981',
24542
+ after: '#f59e0b',
24543
+ api: '#8b5cf6',
24544
+ webhook: '#ec4899',
24545
+ other: '#6b7280'
24546
+ };
24547
+ var triggerBarHtml;
24548
+ if (last7.length === 0) {
24549
+ triggerBarHtml = '<div class="mini-fails-empty">No runs in 7d</div>';
24550
+ } else {
24551
+ triggerBarHtml = '<div class="mini-split">';
24552
+ var legendHtml = '<div class="mini-split-legend">';
24553
+ for (var tk = 0; tk < triggerOrder.length; tk++) {
24554
+ var key = triggerOrder[tk];
24555
+ var n = triggerCounts[key];
24556
+ if (n === 0) continue;
24557
+ var pctW = Math.max(2, Math.round((n / last7.length) * 100));
24558
+ triggerBarHtml += '<div class="mini-split-seg" style="background:' + triggerColors[key] + ';width:' + pctW + '%" title="' + key + ': ' + n + ' run' + (n === 1 ? '' : 's') + '">' + (pctW >= 14 ? key : '') + '</div>';
24559
+ legendHtml += '<span><span class="mini-split-legend-dot" style="background:' + triggerColors[key] + '"></span>' + key + ' ' + n + '</span>';
24560
+ }
24561
+ triggerBarHtml += '</div>' + legendHtml + '</div>';
24562
+ }
24563
+ var activitySub = last7.length === 0
24564
+ ? 'no runs yet'
24565
+ : (busiestCount > 0 ? runsPerDay + ' runs/day · busiest: ' + busiestTask + ' (' + busiestCount + ')' : runsPerDay + ' runs/day');
24566
+
24370
24567
  // ── Compose ────────────────────────────────────────────────────────
24371
24568
  host.innerHTML =
24372
24569
  '<div class="mini-card">'
@@ -24383,6 +24580,11 @@ async function refreshMiniDashboards() {
24383
24580
  + '<div class="mini-card-head"><span class="mini-card-title">Reliability · 7d</span><span class="mini-card-figure">' + totalFails7 + ' fail' + (totalFails7 === 1 ? '' : 's') + '</span></div>'
24384
24581
  + failHtml
24385
24582
  + '<div class="mini-card-sub">click a column in the run list to filter by category</div>'
24583
+ + '</div>'
24584
+ + '<div class="mini-card">'
24585
+ + '<div class="mini-card-head"><span class="mini-card-title">Activity · 7d</span><span class="mini-card-figure">' + last7.length + ' run' + (last7.length === 1 ? '' : 's') + '</span></div>'
24586
+ + triggerBarHtml
24587
+ + '<div class="mini-card-sub">' + esc(activitySub) + '</div>'
24386
24588
  + '</div>';
24387
24589
  }
24388
24590
 
@@ -24514,7 +24716,7 @@ function renderRunListBody(allRuns) {
24514
24716
  if (catOptions.length > 1) {
24515
24717
  html += _runListChip('Category', catOptions, 'filterCategory');
24516
24718
  }
24517
- 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)">';
24719
+ html += '<input type="search" id="runlist-filter-text" 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)">';
24518
24720
  html += '<button class="btn-sm" onclick="resetRunListFilters()" style="font-size:11px">Reset to default</button>';
24519
24721
  html += '</div>';
24520
24722
  if (filtered.length === 0) {
@@ -25126,6 +25328,92 @@ function openRunOrTrace(jobName, runId) {
25126
25328
  return openTraceViewer(jobName);
25127
25329
  }
25128
25330
 
25331
+ // ── PRD §13 / 1.18.94: Replay tooling v1 ────────────────────────────────
25332
+ // Three small handlers wired to the buttons in the Run detail header.
25333
+ // They reuse existing endpoints — no new server-side surface. Future
25334
+ // versions add Rerun-from-step (needs SDK resume support) + Bulk replay.
25335
+ async function replayRerunRun(jobName) {
25336
+ if (!jobName) return;
25337
+ if (!confirm('Rerun "' + jobName + '" with the same prompt? This fires a new run immediately.')) return;
25338
+ try {
25339
+ var r = await apiFetch('/api/cron/run/' + encodeURIComponent(jobName), { method: 'POST' });
25340
+ var d = await r.json().catch(function() { return {}; });
25341
+ if (!r.ok) {
25342
+ // 409 = the task is already running (concurrency lock); explain that.
25343
+ if (r.status === 409) {
25344
+ toast(d.error || (jobName + ' is already running. Cancel the in-flight run first.'), 'error');
25345
+ } else {
25346
+ toast(d.error || ('Rerun failed (HTTP ' + r.status + ')'), 'error');
25347
+ }
25348
+ return;
25349
+ }
25350
+ toast('Rerunning ' + jobName + ' — watch the run list for the new entry.', 'success');
25351
+ // Don't auto-close the modal — the user just confirmed they want to
25352
+ // see the result. The new run will appear in the run-selector dropdown
25353
+ // (loaded by openTraceViewer's history fetch) within a few seconds.
25354
+ setTimeout(function() {
25355
+ if (typeof refreshCron === 'function') refreshCron();
25356
+ if (typeof refreshRunList === 'function') refreshRunList();
25357
+ }, 1200);
25358
+ } catch (e) { toast('Rerun failed: ' + e, 'error'); }
25359
+ }
25360
+
25361
+ async function replayCopyPrompt(jobName) {
25362
+ if (!jobName) return;
25363
+ try {
25364
+ // /api/cron returns the full job list with prompt fields — pull the
25365
+ // matching one and stuff it onto the clipboard. Cheap and avoids a
25366
+ // dedicated GET-by-name endpoint.
25367
+ var r = await apiFetch('/api/cron');
25368
+ var d = await r.json();
25369
+ var jobs = (d && d.jobs) || [];
25370
+ var found = null;
25371
+ for (var i = 0; i < jobs.length; i++) {
25372
+ if (String(jobs[i].name).toLowerCase() === String(jobName).toLowerCase()) { found = jobs[i]; break; }
25373
+ }
25374
+ if (!found || !found.prompt) {
25375
+ toast('No prompt found for ' + jobName, 'error');
25376
+ return;
25377
+ }
25378
+ if (navigator.clipboard && navigator.clipboard.writeText) {
25379
+ await navigator.clipboard.writeText(found.prompt);
25380
+ toast('Prompt copied to clipboard (' + found.prompt.length + ' chars).', 'success');
25381
+ } else {
25382
+ // Fallback for clipboard-less environments (rare in modern browsers,
25383
+ // but the dashboard runs over plain HTTP on localhost which can hit
25384
+ // the secure-context restriction in some setups).
25385
+ var ta = document.createElement('textarea');
25386
+ ta.value = found.prompt;
25387
+ document.body.appendChild(ta);
25388
+ ta.select();
25389
+ try { document.execCommand('copy'); toast('Prompt copied (legacy mode).', 'success'); }
25390
+ catch (e) { toast('Copy not supported in this browser. Open the editor instead.', 'error'); }
25391
+ document.body.removeChild(ta);
25392
+ }
25393
+ } catch (e) { toast('Copy prompt failed: ' + e, 'error'); }
25394
+ }
25395
+
25396
+ function replayOpenRunList(jobName) {
25397
+ if (!jobName) return;
25398
+ // Close the trace modal, switch to Run list tab, set the text filter to
25399
+ // the task name. The Run list already supports free-text task-name match.
25400
+ try { document.getElementById('trace-modal').classList.remove('show'); } catch (e) { /* ignore */ }
25401
+ // The Tasks page has three top-level Build tabs; the Run list lives on
25402
+ // tab #3. Switch via the existing tab handler if available; otherwise
25403
+ // fall back to setting the URL hash so the page handler picks it up.
25404
+ if (typeof switchBuildTab === 'function') {
25405
+ switchBuildTab('runs');
25406
+ }
25407
+ if (typeof _runListState !== 'undefined') {
25408
+ _runListState.filterText = jobName;
25409
+ if (typeof refreshRunList === 'function') refreshRunList();
25410
+ // Mirror filter into the visible input so users see why the list is filtered.
25411
+ var inp = document.getElementById('runlist-filter-text');
25412
+ if (inp) inp.value = jobName;
25413
+ }
25414
+ toast('Filtered run list to "' + jobName + '"', 'success');
25415
+ }
25416
+
25129
25417
  // PRD Phase 4b / 1.18.86: Run detail viewer. Renders a waterfall of
25130
25418
  // RunEvent rows from /api/runs/:runId/events. Color-coded by kind, paired
25131
25419
  // tool_call→tool_result by toolUseId, with expandable per-span content.
@@ -25203,6 +25491,19 @@ function renderRunDetailWaterfall(events, runId, jobName) {
25203
25491
  + '<span style="flex:1"></span>'
25204
25492
  + '<code style="font-size:10px;color:var(--text-muted)">runId ' + esc(String(runId).slice(0, 12)) + '…</code>'
25205
25493
  + '</div>'
25494
+ // PRD §13 / 1.18.94 — Replay tooling v1. Three actions reachable from
25495
+ // every Run detail: rerun the same task with the same prompt (kicks off
25496
+ // a new run via the existing /api/cron/run/:job endpoint with
25497
+ // trigger='manual'), copy the prompt to clipboard for quick edits in
25498
+ // the editor, and jump to the Run list filtered to this task name.
25499
+ // Only rendered when we know the jobName (not for orphaned runs).
25500
+ + (jobName
25501
+ ? '<div style="display:flex;gap:8px;margin-top:10px;flex-wrap:wrap">'
25502
+ + '<button class="btn-sm btn-success" onclick="replayRerunRun(\\x27' + jsStr(jobName) + '\\x27)" title="Fire this task once — same prompt, fresh run">▶ Rerun task</button>'
25503
+ + '<button class="btn-sm" onclick="replayCopyPrompt(\\x27' + jsStr(jobName) + '\\x27)" title="Copy the prompt for this task to your clipboard">⧉ Copy prompt</button>'
25504
+ + '<button class="btn-sm" onclick="replayOpenRunList(\\x27' + jsStr(jobName) + '\\x27)" title="Open the Run list filtered to this task">↗ Open run list</button>'
25505
+ + '</div>'
25506
+ : '')
25206
25507
  + '</div>';
25207
25508
 
25208
25509
  // Waterfall rows
@@ -33265,6 +33566,9 @@ async function refreshTeam() {
33265
33566
  '<div class="office-hero-stat"><div class="stat-val">' + fmtTokens(clemTokenTotal) + '</div><div class="stat-lbl">Tokens</div></div>' +
33266
33567
  '<div class="office-hero-stat"><div class="stat-val">' + (clem.crons ? clem.crons.total : 0) + '</div><div class="stat-lbl">Cron Jobs</div></div>' +
33267
33568
  '</div>' +
33569
+ '<div class="office-hero-actions" style="margin-left:auto;display:flex;gap:6px">' +
33570
+ '<button class="btn btn-sm" onclick="editClementine()" title="Configure Clementine\\x27s persona, tools, and limits">Edit</button>' +
33571
+ '</div>' +
33268
33572
  '</div>' +
33269
33573
  (needsDiscordSetup ?
33270
33574
  '<div class="discord-setup-banner" id="discord-setup-banner">' +
@@ -34,9 +34,9 @@ export const clementineJsonSchema = z.object({
34
34
  autonomy: z.enum(['ask_first', 'balanced', 'act_when_safe']).optional(),
35
35
  /**
36
36
  * Dashboard-managed profile for the main agent (Clementine herself).
37
- * Mirrors the per-agent edit surface so Tasks → Team can edit the
38
- * primary persona without forcing users into Settings or env files.
39
- * Every field is optional; absent fields fall through to the existing
37
+ * Mirrors the per-agent edit surface so Tasks → Team can edit her
38
+ * persona, tools, and limits without forcing users into Settings or
39
+ * env files. Every field is optional absent fields fall through to
40
40
  * env / compiled defaults via computeEffectiveConfig.
41
41
  */
42
42
  profile: z.object({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clementine-agent",
3
- "version": "1.18.93",
3
+ "version": "1.18.96",
4
4
  "description": "Clementine — Personal AI Assistant (TypeScript)",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",