clementine-agent 1.18.93 → 1.18.95

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.
@@ -10190,6 +10190,12 @@ If the tool returns nothing or errors, return an empty array \`[]\`.`,
10190
10190
  sendPolicy: a.sendPolicy ?? null,
10191
10191
  agentStatus: a.status ?? 'active',
10192
10192
  budgetMonthlyCents: a.budgetMonthlyCents ?? 0,
10193
+ // Mission & persona — what the agent edit modal renders in
10194
+ // its single combined textarea.
10195
+ systemPromptBody: a.systemPromptBody ?? '',
10196
+ // Multi-project access list (separate from the legacy single
10197
+ // `project` binding above).
10198
+ projects: a.projects ?? [],
10193
10199
  };
10194
10200
  }));
10195
10201
  }
@@ -14636,6 +14642,103 @@ if('serviceWorker' in navigator){navigator.serviceWorker.getRegistrations().then
14636
14642
  font-weight: 600;
14637
14643
  }
14638
14644
 
14645
+ /* ── Agent edit modal — tabs + Equipment panel ─────────────────── */
14646
+ .agent-tab {
14647
+ background: transparent;
14648
+ border: 0;
14649
+ border-bottom: 2px solid transparent;
14650
+ color: var(--text-muted);
14651
+ cursor: pointer;
14652
+ font-size: 13px;
14653
+ padding: 8px 14px;
14654
+ margin-bottom: -1px;
14655
+ }
14656
+ .agent-tab:hover { color: var(--text-primary); }
14657
+ .agent-tab.active {
14658
+ color: var(--text-primary);
14659
+ border-bottom-color: var(--clementine);
14660
+ font-weight: 600;
14661
+ }
14662
+ .agent-tools-filter {
14663
+ background: transparent;
14664
+ border: 1px solid var(--border);
14665
+ border-radius: 4px;
14666
+ color: var(--text-muted);
14667
+ cursor: pointer;
14668
+ font-size: 11px;
14669
+ padding: 4px 8px;
14670
+ }
14671
+ .agent-tools-filter:hover { color: var(--text-primary); }
14672
+ .agent-tools-filter.active {
14673
+ background: var(--clementine);
14674
+ border-color: var(--clementine);
14675
+ color: #000;
14676
+ font-weight: 600;
14677
+ }
14678
+ .agent-tool-row {
14679
+ align-items: center;
14680
+ color: var(--text-primary);
14681
+ cursor: pointer;
14682
+ display: flex;
14683
+ font-size: 12px;
14684
+ gap: 6px;
14685
+ line-height: 1.4;
14686
+ padding: 4px 6px;
14687
+ border-radius: 4px;
14688
+ }
14689
+ .agent-tool-row:hover { background: rgba(255,255,255,0.03); }
14690
+ .agent-tool-row .tt-name { flex: 1; min-width: 0; }
14691
+ .agent-tool-row .tt-badge {
14692
+ background: rgba(255,255,255,0.06);
14693
+ border-radius: 3px;
14694
+ color: var(--text-muted);
14695
+ font-size: 9px;
14696
+ font-weight: 700;
14697
+ letter-spacing: 0.4px;
14698
+ padding: 1px 5px;
14699
+ text-transform: uppercase;
14700
+ }
14701
+ .agent-tool-row .tt-badge.cli { background: rgba(120, 200, 255, 0.12); color: #79c8ff; }
14702
+ .agent-tool-row .tt-badge.sdk { background: rgba(168, 200, 255, 0.12); color: #a8c8ff; }
14703
+ .agent-tool-row .tt-badge.mcp { background: rgba(180, 130, 255, 0.12); color: #c8a8ff; }
14704
+ .agent-tool-row .tt-badge.api { background: rgba(255, 200, 120, 0.12); color: #ffc878; }
14705
+ .agent-tool-row .tt-badge.composio { background: rgba(255, 138, 138, 0.14); color: #ff8a8a; }
14706
+ .agent-tool-row .tt-badge.project, .agent-tool-row .tt-badge['project-mcp'] { background: rgba(120, 255, 180, 0.12); color: #78ffb4; }
14707
+ .agent-tool-row .tt-status {
14708
+ align-items: center;
14709
+ color: var(--text-muted);
14710
+ display: inline-flex;
14711
+ font-size: 10px;
14712
+ gap: 3px;
14713
+ }
14714
+ .agent-tool-row .tt-status .tt-dot {
14715
+ border-radius: 50%;
14716
+ height: 7px;
14717
+ width: 7px;
14718
+ }
14719
+ .agent-tool-row .tt-status.ready .tt-dot { background: var(--green); }
14720
+ .agent-tool-row .tt-status.needs-setup .tt-dot { background: #f59e0b; }
14721
+ .agent-tool-row .tt-status.not-installed .tt-dot { background: #888; }
14722
+ .agent-tool-row .tt-status.blocked .tt-dot { background: #ef4444; }
14723
+ .agent-tool-cat {
14724
+ color: var(--text-muted);
14725
+ cursor: pointer;
14726
+ font-size: 11px;
14727
+ font-weight: 700;
14728
+ letter-spacing: 0.5px;
14729
+ margin: 10px 0 4px;
14730
+ text-transform: uppercase;
14731
+ user-select: none;
14732
+ }
14733
+ .agent-tool-cat .cat-count {
14734
+ color: var(--text-muted);
14735
+ font-weight: 500;
14736
+ margin-left: 6px;
14737
+ text-transform: none;
14738
+ letter-spacing: 0;
14739
+ }
14740
+ .agent-tool-row.hidden, .agent-tool-cat.hidden { display: none; }
14741
+
14639
14742
  /* ── Office Hero — Clementine ─────────── */
14640
14743
  .office-hero {
14641
14744
  background: var(--bg-card);
@@ -19984,9 +20087,13 @@ if('serviceWorker' in navigator){navigator.serviceWorker.getRegistrations().then
19984
20087
  </div>
19985
20088
  </div>
19986
20089
 
19987
- <!-- Agent Create/Edit Modal -->
20090
+ <!-- Agent Create/Edit Modal — tabbed editor reused for hired agents
20091
+ AND for Clementine herself (see editClementine() in dashboard JS).
20092
+ Element IDs are stable so existing populator/submit code keeps
20093
+ working; new tabs (Equipment, Goals) add fields without breaking the
20094
+ old contract. -->
19988
20095
  <div id="agent-modal" class="modal-overlay">
19989
- <div class="modal" style="width:520px">
20096
+ <div class="modal" style="width:760px;max-width:95vw">
19990
20097
  <div class="modal-header">
19991
20098
  <h3 id="agent-modal-title">Hire a New Team Member</h3>
19992
20099
  <button class="btn-ghost btn-sm" onclick="hideAgentModal()">&times;</button>
@@ -19994,26 +20101,82 @@ if('serviceWorker' in navigator){navigator.serviceWorker.getRegistrations().then
19994
20101
  <div class="modal-body">
19995
20102
  <form id="agent-form" onsubmit="submitAgentForm(event)">
19996
20103
  <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">
20104
+ <input type="hidden" id="agent-edit-mode" value="agent"><!-- 'agent' or 'clementine' -->
20105
+
20106
+ <!-- Tab strip ---------------------------------------------------- -->
20107
+ <div id="agent-modal-tabs" style="display:flex;gap:2px;border-bottom:1px solid var(--border);margin-bottom:14px">
20108
+ <button type="button" class="agent-tab active" data-tab="identity" onclick="switchAgentTab('identity')">Identity</button>
20109
+ <button type="button" class="agent-tab" data-tab="equipment" onclick="switchAgentTab('equipment')">Equipment</button>
20110
+ <button type="button" class="agent-tab" data-tab="connections" onclick="switchAgentTab('connections')">Connections</button>
20111
+ <button type="button" class="agent-tab" data-tab="goals" onclick="switchAgentTab('goals')">Goals</button>
20112
+ <button type="button" class="agent-tab" data-tab="limits" onclick="switchAgentTab('limits')">Limits</button>
20004
20113
  </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">
20114
+
20115
+ <!-- Tab: Identity ------------------------------------------------ -->
20116
+ <div class="agent-tab-pane" data-tab-pane="identity">
20117
+ <div style="display:grid;grid-template-columns:1fr 1fr;gap:12px;margin-bottom:12px">
20118
+ <div>
20119
+ <label style="display:block;color:var(--text-muted);font-size:12px;margin-bottom:4px">Name *</label>
20120
+ <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">
20121
+ </div>
20122
+ <div>
20123
+ <label style="display:block;color:var(--text-muted);font-size:12px;margin-bottom:4px">Model</label>
20124
+ <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)">
20125
+ <option value="">Default (Sonnet)</option>
20126
+ <option value="haiku">Haiku</option>
20127
+ <option value="sonnet">Sonnet</option>
20128
+ <option value="opus">Opus</option>
20129
+ </select>
20130
+ </div>
20131
+ </div>
20132
+ <div id="agent-identity-row-2" style="margin-bottom:12px">
20133
+ <label style="display:block;color:var(--text-muted);font-size:12px;margin-bottom:4px">Profile Photo URL</label>
20134
+ <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">
20135
+ </div>
20136
+ <div style="margin-bottom:12px">
20137
+ <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>
20138
+ <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">
20139
+ </div>
20140
+ <div style="margin-bottom:12px">
20141
+ <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>
20142
+ <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>
20143
+ </div>
20144
+ <div id="agent-project-row" style="margin-bottom:12px">
20145
+ <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>
20146
+ <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)">
20147
+ <option value="">None</option>
20148
+ </select>
20149
+ </div>
20008
20150
  </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>
20151
+
20152
+ <!-- Tab: Equipment ----------------------------------------------- -->
20153
+ <div class="agent-tab-pane" data-tab-pane="equipment" style="display:none">
20154
+ <div style="display:flex;gap:8px;align-items:center;margin-bottom:8px;flex-wrap:wrap">
20155
+ <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">
20156
+ <div id="agent-tools-filters" style="display:flex;gap:4px">
20157
+ <button type="button" class="agent-tools-filter active" data-filter="all" onclick="setAgentToolsFilter('all')">All</button>
20158
+ <button type="button" class="agent-tools-filter" data-filter="enabled" onclick="setAgentToolsFilter('enabled')">Enabled</button>
20159
+ <button type="button" class="agent-tools-filter" data-filter="ready" onclick="setAgentToolsFilter('ready')">Ready</button>
20160
+ <button type="button" class="agent-tools-filter" data-filter="needs-setup" onclick="setAgentToolsFilter('needs-setup')">Needs setup</button>
20161
+ </div>
20162
+ </div>
20163
+ <div id="agent-tools-summary" style="font-size:11px;color:var(--text-muted);margin-bottom:6px">Loading…</div>
20164
+ <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)">
20165
+ <div style="color:var(--text-muted);font-size:12px">Loading tools...</div>
20166
+ </div>
20167
+ <div style="margin-top:8px;font-size:11px;color:var(--text-muted);line-height:1.5">
20168
+ <span style="display:inline-block;width:8px;height:8px;border-radius:50%;background:var(--green);vertical-align:middle;margin-right:4px"></span>Ready &nbsp;
20169
+ <span style="display:inline-block;width:8px;height:8px;border-radius:50%;background:#f59e0b;vertical-align:middle;margin-right:4px"></span>Needs setup &nbsp;
20170
+ <span style="display:inline-block;width:8px;height:8px;border-radius:50%;background:#888;vertical-align:middle;margin-right:4px"></span>Not installed &nbsp;
20171
+ <span style="display:inline-block;width:8px;height:8px;border-radius:50%;background:#ef4444;vertical-align:middle;margin-right:4px"></span>Blocked
20172
+ </div>
20012
20173
  </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)">
20174
+
20175
+ <!-- Tab: Connections -------------------------------------------- -->
20176
+ <div class="agent-tab-pane" data-tab-pane="connections" style="display:none">
20177
+ <div style="margin-bottom:14px">
20178
+ <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>
20179
+ <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
20180
  <div style="color:var(--text-muted);font-size:12px">Loading channels...</div>
20018
20181
  </div>
20019
20182
  <label style="display:flex;align-items:center;gap:6px;margin-top:6px;color:var(--text-muted);font-size:12px;cursor:pointer">
@@ -20025,76 +20188,15 @@ if('serviceWorker' in navigator){navigator.serviceWorker.getRegistrations().then
20025
20188
  Respond to all messages <span style="opacity:0.6">(not just @mentions)</span>
20026
20189
  </label>
20027
20190
  </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>
20191
+ <div style="margin-bottom:14px">
20192
+ <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>
20193
+ <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
20194
  </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>
20195
+ <div style="margin-bottom:14px">
20196
+ <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>
20197
+ <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
20198
  </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
-
20199
+ <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
20200
  <details id="discord-section" style="margin-bottom:10px">
20099
20201
  <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
20202
  <div style="padding-left:8px">
@@ -20139,8 +20241,53 @@ if('serviceWorker' in navigator){navigator.serviceWorker.getRegistrations().then
20139
20241
  </div>
20140
20242
  </div>
20141
20243
  </details>
20244
+ </div><!-- /agent-tab-pane connections -->
20245
+
20246
+ <!-- Tab: Goals --------------------------------------------------- -->
20247
+ <div class="agent-tab-pane" data-tab-pane="goals" style="display:none">
20248
+ <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>
20249
+ <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)">
20250
+ <div style="color:var(--text-muted);font-size:12px">Loading goals…</div>
20251
+ </div>
20142
20252
  </div>
20143
- <div style="display:flex;gap:8px;justify-content:flex-end">
20253
+
20254
+ <!-- Tab: Limits -------------------------------------------------- -->
20255
+ <div class="agent-tab-pane" data-tab-pane="limits" style="display:none">
20256
+ <div style="display:grid;grid-template-columns:1fr 1fr;gap:12px;margin-bottom:14px">
20257
+ <div id="agent-tier-row">
20258
+ <label style="display:block;color:var(--text-muted);font-size:12px;margin-bottom:4px">Security Clearance</label>
20259
+ <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)">
20260
+ <option value="2">Tier 2 (Read/Write)</option>
20261
+ <option value="1">Tier 1 (Read-only)</option>
20262
+ </select>
20263
+ </div>
20264
+ <div>
20265
+ <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>
20266
+ <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">
20267
+ </div>
20268
+ </div>
20269
+ <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>
20270
+ <div style="display:grid;grid-template-columns:1fr 1fr;gap:8px;margin-bottom:8px">
20271
+ <div>
20272
+ <label style="display:block;color:var(--text-muted);font-size:11px;margin-bottom:3px">Max Emails / Day</label>
20273
+ <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">
20274
+ </div>
20275
+ <div>
20276
+ <label style="display:block;color:var(--text-muted);font-size:11px;margin-bottom:3px">Approval Mode</label>
20277
+ <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">
20278
+ <option value="">Disabled (no autonomous sending)</option>
20279
+ <option value="none">None (fully autonomous)</option>
20280
+ <option value="first-in-sequence">First in sequence (approve first email per lead)</option>
20281
+ <option value="all">All (approve every send)</option>
20282
+ </select>
20283
+ </div>
20284
+ </div>
20285
+ <label style="display:flex;align-items:center;gap:6px;color:var(--text-muted);font-size:12px;cursor:pointer">
20286
+ <input id="agent-send-biz-hours" type="checkbox"> Restrict to business hours (8am–6pm)
20287
+ </label>
20288
+ </div>
20289
+
20290
+ <div style="display:flex;gap:8px;justify-content:flex-end;margin-top:16px;border-top:1px solid var(--border);padding-top:12px">
20144
20291
  <button type="button" class="btn" onclick="hideAgentModal()">Cancel</button>
20145
20292
  <button type="submit" class="btn" style="background:var(--green);color:#000;font-weight:600" id="agent-submit-btn">Complete Hiring</button>
20146
20293
  </div>
@@ -24514,7 +24661,7 @@ function renderRunListBody(allRuns) {
24514
24661
  if (catOptions.length > 1) {
24515
24662
  html += _runListChip('Category', catOptions, 'filterCategory');
24516
24663
  }
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)">';
24664
+ 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
24665
  html += '<button class="btn-sm" onclick="resetRunListFilters()" style="font-size:11px">Reset to default</button>';
24519
24666
  html += '</div>';
24520
24667
  if (filtered.length === 0) {
@@ -25126,6 +25273,92 @@ function openRunOrTrace(jobName, runId) {
25126
25273
  return openTraceViewer(jobName);
25127
25274
  }
25128
25275
 
25276
+ // ── PRD §13 / 1.18.94: Replay tooling v1 ────────────────────────────────
25277
+ // Three small handlers wired to the buttons in the Run detail header.
25278
+ // They reuse existing endpoints — no new server-side surface. Future
25279
+ // versions add Rerun-from-step (needs SDK resume support) + Bulk replay.
25280
+ async function replayRerunRun(jobName) {
25281
+ if (!jobName) return;
25282
+ if (!confirm('Rerun "' + jobName + '" with the same prompt? This fires a new run immediately.')) return;
25283
+ try {
25284
+ var r = await apiFetch('/api/cron/run/' + encodeURIComponent(jobName), { method: 'POST' });
25285
+ var d = await r.json().catch(function() { return {}; });
25286
+ if (!r.ok) {
25287
+ // 409 = the task is already running (concurrency lock); explain that.
25288
+ if (r.status === 409) {
25289
+ toast(d.error || (jobName + ' is already running. Cancel the in-flight run first.'), 'error');
25290
+ } else {
25291
+ toast(d.error || ('Rerun failed (HTTP ' + r.status + ')'), 'error');
25292
+ }
25293
+ return;
25294
+ }
25295
+ toast('Rerunning ' + jobName + ' — watch the run list for the new entry.', 'success');
25296
+ // Don't auto-close the modal — the user just confirmed they want to
25297
+ // see the result. The new run will appear in the run-selector dropdown
25298
+ // (loaded by openTraceViewer's history fetch) within a few seconds.
25299
+ setTimeout(function() {
25300
+ if (typeof refreshCron === 'function') refreshCron();
25301
+ if (typeof refreshRunList === 'function') refreshRunList();
25302
+ }, 1200);
25303
+ } catch (e) { toast('Rerun failed: ' + e, 'error'); }
25304
+ }
25305
+
25306
+ async function replayCopyPrompt(jobName) {
25307
+ if (!jobName) return;
25308
+ try {
25309
+ // /api/cron returns the full job list with prompt fields — pull the
25310
+ // matching one and stuff it onto the clipboard. Cheap and avoids a
25311
+ // dedicated GET-by-name endpoint.
25312
+ var r = await apiFetch('/api/cron');
25313
+ var d = await r.json();
25314
+ var jobs = (d && d.jobs) || [];
25315
+ var found = null;
25316
+ for (var i = 0; i < jobs.length; i++) {
25317
+ if (String(jobs[i].name).toLowerCase() === String(jobName).toLowerCase()) { found = jobs[i]; break; }
25318
+ }
25319
+ if (!found || !found.prompt) {
25320
+ toast('No prompt found for ' + jobName, 'error');
25321
+ return;
25322
+ }
25323
+ if (navigator.clipboard && navigator.clipboard.writeText) {
25324
+ await navigator.clipboard.writeText(found.prompt);
25325
+ toast('Prompt copied to clipboard (' + found.prompt.length + ' chars).', 'success');
25326
+ } else {
25327
+ // Fallback for clipboard-less environments (rare in modern browsers,
25328
+ // but the dashboard runs over plain HTTP on localhost which can hit
25329
+ // the secure-context restriction in some setups).
25330
+ var ta = document.createElement('textarea');
25331
+ ta.value = found.prompt;
25332
+ document.body.appendChild(ta);
25333
+ ta.select();
25334
+ try { document.execCommand('copy'); toast('Prompt copied (legacy mode).', 'success'); }
25335
+ catch (e) { toast('Copy not supported in this browser. Open the editor instead.', 'error'); }
25336
+ document.body.removeChild(ta);
25337
+ }
25338
+ } catch (e) { toast('Copy prompt failed: ' + e, 'error'); }
25339
+ }
25340
+
25341
+ function replayOpenRunList(jobName) {
25342
+ if (!jobName) return;
25343
+ // Close the trace modal, switch to Run list tab, set the text filter to
25344
+ // the task name. The Run list already supports free-text task-name match.
25345
+ try { document.getElementById('trace-modal').classList.remove('show'); } catch (e) { /* ignore */ }
25346
+ // The Tasks page has three top-level Build tabs; the Run list lives on
25347
+ // tab #3. Switch via the existing tab handler if available; otherwise
25348
+ // fall back to setting the URL hash so the page handler picks it up.
25349
+ if (typeof switchBuildTab === 'function') {
25350
+ switchBuildTab('runs');
25351
+ }
25352
+ if (typeof _runListState !== 'undefined') {
25353
+ _runListState.filterText = jobName;
25354
+ if (typeof refreshRunList === 'function') refreshRunList();
25355
+ // Mirror filter into the visible input so users see why the list is filtered.
25356
+ var inp = document.getElementById('runlist-filter-text');
25357
+ if (inp) inp.value = jobName;
25358
+ }
25359
+ toast('Filtered run list to "' + jobName + '"', 'success');
25360
+ }
25361
+
25129
25362
  // PRD Phase 4b / 1.18.86: Run detail viewer. Renders a waterfall of
25130
25363
  // RunEvent rows from /api/runs/:runId/events. Color-coded by kind, paired
25131
25364
  // tool_call→tool_result by toolUseId, with expandable per-span content.
@@ -25203,6 +25436,19 @@ function renderRunDetailWaterfall(events, runId, jobName) {
25203
25436
  + '<span style="flex:1"></span>'
25204
25437
  + '<code style="font-size:10px;color:var(--text-muted)">runId ' + esc(String(runId).slice(0, 12)) + '…</code>'
25205
25438
  + '</div>'
25439
+ // PRD §13 / 1.18.94 — Replay tooling v1. Three actions reachable from
25440
+ // every Run detail: rerun the same task with the same prompt (kicks off
25441
+ // a new run via the existing /api/cron/run/:job endpoint with
25442
+ // trigger='manual'), copy the prompt to clipboard for quick edits in
25443
+ // the editor, and jump to the Run list filtered to this task name.
25444
+ // Only rendered when we know the jobName (not for orphaned runs).
25445
+ + (jobName
25446
+ ? '<div style="display:flex;gap:8px;margin-top:10px;flex-wrap:wrap">'
25447
+ + '<button class="btn-sm btn-success" onclick="replayRerunRun(\\x27' + jsStr(jobName) + '\\x27)" title="Fire this task once — same prompt, fresh run">▶ Rerun task</button>'
25448
+ + '<button class="btn-sm" onclick="replayCopyPrompt(\\x27' + jsStr(jobName) + '\\x27)" title="Copy the prompt for this task to your clipboard">⧉ Copy prompt</button>'
25449
+ + '<button class="btn-sm" onclick="replayOpenRunList(\\x27' + jsStr(jobName) + '\\x27)" title="Open the Run list filtered to this task">↗ Open run list</button>'
25450
+ + '</div>'
25451
+ : '')
25206
25452
  + '</div>';
25207
25453
 
25208
25454
  // Waterfall rows
@@ -33265,6 +33511,9 @@ async function refreshTeam() {
33265
33511
  '<div class="office-hero-stat"><div class="stat-val">' + fmtTokens(clemTokenTotal) + '</div><div class="stat-lbl">Tokens</div></div>' +
33266
33512
  '<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
33513
  '</div>' +
33514
+ '<div class="office-hero-actions" style="margin-left:auto;display:flex;gap:6px">' +
33515
+ '<button class="btn btn-sm" onclick="editClementine()" title="Configure Clementine\\x27s persona, tools, and limits">Edit</button>' +
33516
+ '</div>' +
33268
33517
  '</div>' +
33269
33518
  (needsDiscordSetup ?
33270
33519
  '<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.95",
4
4
  "description": "Clementine — Personal AI Assistant (TypeScript)",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",