clementine-agent 1.1.11 → 1.1.12

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.
@@ -1517,18 +1517,31 @@ You have a cost budget per message — not a hard turn limit. Work until the tas
1517
1517
  const { detectFrustrationSignals, detectRepeatedTopics } = require('./insight-engine.js');
1518
1518
  const since24h = new Date(Date.now() - 24 * 60 * 60 * 1000).toISOString();
1519
1519
  const since7d = new Date(Date.now() - 7 * 24 * 60 * 60 * 1000).toISOString();
1520
- const recent = this.getRecentActivity(since24h, 50);
1521
- const week = this.getRecentActivity(since7d, 200);
1520
+ let recent = this.getRecentActivity(since24h, 50);
1521
+ let week = this.getRecentActivity(since7d, 200);
1522
+ // Phase 10c: per-agent scope filter. Per-agent bot session keys
1523
+ // embed the agent slug (e.g. dm:ross-the-sdr:userId), so when this
1524
+ // prompt is for a specific agent profile we only consider sessions
1525
+ // that involved THAT agent. Without this filter, Nate's frustration
1526
+ // chatting with Sasha would leak into Ross's prompt — wrong signal.
1527
+ if (profile?.slug) {
1528
+ const slugMarker = `:${profile.slug}:`;
1529
+ recent = recent.filter(e => e.sessionKey.includes(slugMarker));
1530
+ week = week.filter(e => e.sessionKey.includes(slugMarker));
1531
+ }
1522
1532
  const frustration = detectFrustrationSignals(recent);
1523
1533
  const topics = detectRepeatedTopics(week);
1524
1534
  const allSignals = [...frustration, ...topics];
1525
1535
  if (allSignals.length > 0) {
1536
+ const scopeNote = profile?.slug
1537
+ ? `\n\n*Scope: signals from sessions with you (${profile.slug}).*`
1538
+ : '';
1526
1539
  const guidance = frustration.length > 0
1527
1540
  ? '\n\n**Adjust your approach:** When friction signals are present, lead with a clarifying question instead of assuming. Acknowledge the prior misunderstanding briefly without over-apologizing. Confirm understanding before acting.'
1528
1541
  : '\n\n**Use this context naturally:** Recurring topics may indicate an unresolved thread — if relevant, offer to close the loop or summarize current state. Do not force callbacks if not directly applicable.';
1529
1542
  volatileParts.push(`## Conversational Context\n\nSignals from recent sessions:\n` +
1530
1543
  allSignals.map(s => `- ${s}`).join('\n') +
1531
- guidance);
1544
+ guidance + scopeNote);
1532
1545
  }
1533
1546
  }
1534
1547
  catch { /* non-fatal — insight-engine optional */ }
@@ -16981,6 +16981,51 @@ async function refreshToolUsagePanel() {
16981
16981
  }
16982
16982
  html += '</table>';
16983
16983
  }
16984
+
16985
+ // Phase 11e: cost-by-source pivot — aggregates the bySource maps from
16986
+ // every family into a single "top jobs by spend" view. Uses only data
16987
+ // already in the response, no extra API call.
16988
+ const sourceTotals = {};
16989
+ for (const f of (data.families || [])) {
16990
+ const callsPerSource = {};
16991
+ for (const s of (f.bySource || [])) callsPerSource[s.source] = s.count;
16992
+ const familyTotalCalls = f.totalCalls || 0;
16993
+ for (const s of (f.bySource || [])) {
16994
+ const share = familyTotalCalls > 0 ? s.count / familyTotalCalls : 0;
16995
+ const cost = (f.estimatedCostUsd || 0) * share;
16996
+ if (!sourceTotals[s.source]) sourceTotals[s.source] = { source: s.source, cost: 0, calls: 0 };
16997
+ sourceTotals[s.source].cost += cost;
16998
+ sourceTotals[s.source].calls += s.count;
16999
+ }
17000
+ }
17001
+ const sourceList = Object.values(sourceTotals)
17002
+ .filter(s => s.source !== 'unknown' || sourceTotals.unknown.cost > 0)
17003
+ .sort((a, b) => b.cost - a.cost);
17004
+
17005
+ if (sourceList.length > 0) {
17006
+ const maxSrcCost = Math.max.apply(null, sourceList.map(s => s.cost).concat([0.0001]));
17007
+ html += '<div style="margin-top:18px"><div style="font-weight:600;font-size:13px;margin-bottom:8px;color:var(--text-secondary)">Top jobs by cost</div>';
17008
+ html += '<table style="width:100%;font-size:13px"><tr>'
17009
+ + '<th>Job / source</th><th style="text-align:right">Cost</th><th style="text-align:right">Share</th><th style="text-align:right">Calls</th><th>Distribution</th></tr>';
17010
+ const totalSrcCost = sourceList.reduce((sum, s) => sum + s.cost, 0);
17011
+ for (const s of sourceList.slice(0, 10)) {
17012
+ const pct = totalSrcCost > 0 ? ((s.cost / totalSrcCost) * 100).toFixed(1) + '%' : '0.0%';
17013
+ const barW = Math.max(2, Math.round((s.cost / maxSrcCost) * 100));
17014
+ const sourceLabel = s.source === 'unknown'
17015
+ ? '<em style="color:var(--text-muted)">unattributed</em>'
17016
+ : '<strong>' + esc(s.source) + '</strong>';
17017
+ html += '<tr>'
17018
+ + '<td>' + sourceLabel + '</td>'
17019
+ + '<td style="text-align:right;color:var(--green)">$' + s.cost.toFixed(2) + '</td>'
17020
+ + '<td style="text-align:right;color:var(--text-muted)">' + pct + '</td>'
17021
+ + '<td style="text-align:right">' + s.calls.toLocaleString() + '</td>'
17022
+ + '<td><div style="background:var(--bg-elev);height:8px;border-radius:4px;overflow:hidden;width:100%;max-width:160px">'
17023
+ + '<div style="background:var(--blue);height:100%;width:' + barW + '%"></div></div></td>'
17024
+ + '</tr>';
17025
+ }
17026
+ html += '</table></div>';
17027
+ }
17028
+
16984
17029
  html += '</div></div>';
16985
17030
  host.innerHTML = html;
16986
17031
  } catch(e) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clementine-agent",
3
- "version": "1.1.11",
3
+ "version": "1.1.12",
4
4
  "description": "Clementine — Personal AI Assistant (TypeScript)",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",