@blockrun/franklin 3.21.2 → 3.21.3

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.
@@ -1990,6 +1990,22 @@ function formatDuration(sec) {
1990
1990
  return m > 0 ? m + 'm ' + s + 's' : s + 's';
1991
1991
  }
1992
1992
 
1993
+ function escapeHtml(value) {
1994
+ return String(value == null ? '' : value).replace(/[&<>"']/g, ch => ({
1995
+ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;'
1996
+ }[ch]));
1997
+ }
1998
+
1999
+ function safeHttpUrl(value) {
2000
+ if (typeof value !== 'string') return '';
2001
+ try {
2002
+ const u = new URL(value);
2003
+ return (u.protocol === 'http:' || u.protocol === 'https:') ? u.href : '';
2004
+ } catch (e) {
2005
+ return '';
2006
+ }
2007
+ }
2008
+
1993
2009
  function renderCallsList(calls) {
1994
2010
  const list = document.getElementById('calls-list');
1995
2011
  if (!list) return;
@@ -2007,13 +2023,14 @@ function renderCallsList(calls) {
2007
2023
  const fromHuman = formatPhoneNumber(c.from);
2008
2024
  const when = new Date(c.timestamp).toLocaleString();
2009
2025
  const cost = c.paid_usd ? '$' + c.paid_usd.toFixed(2) : '—';
2010
- const safeTask = (c.task || '').replace(/[<>&]/g, ch => ({'<':'&lt;','>':'&gt;','&':'&amp;'}[ch]));
2026
+ const safeTask = escapeHtml(c.task || '');
2027
+ const recordingUrl = safeHttpUrl(c.recording_url);
2011
2028
  const transcriptHtml = c.transcript
2012
2029
  ? '<details style="margin-top:8px"><summary style="cursor:pointer;color:var(--text-dim);font-size:12px">Transcript</summary><pre style="white-space:pre-wrap;background:oklch(0 0 0 / 25%);padding:10px;border-radius:8px;font-size:12px;margin-top:6px;max-height:400px;overflow:auto">' +
2013
- c.transcript.replace(/[<>&]/g, ch => ({'<':'&lt;','>':'&gt;','&':'&amp;'}[ch])) + '</pre></details>'
2030
+ escapeHtml(c.transcript) + '</pre></details>'
2014
2031
  : '';
2015
- const recordingHtml = c.recording_url
2016
- ? '<a href="' + c.recording_url + '" target="_blank" rel="noopener" style="font-size:11px;color:var(--brand);text-decoration:none;margin-left:8px">▶ recording</a>'
2032
+ const recordingHtml = recordingUrl
2033
+ ? '<a href="' + escapeHtml(recordingUrl) + '" target="_blank" rel="noopener" style="font-size:11px;color:var(--brand);text-decoration:none;margin-left:8px">▶ recording</a>'
2017
2034
  : '';
2018
2035
  return ''
2019
2036
  + '<div class="phone-row" style="grid-template-columns:auto 1fr auto;align-items:start">'
@@ -2023,12 +2040,12 @@ function renderCallsList(calls) {
2023
2040
  + ' </svg>'
2024
2041
  + ' </div>'
2025
2042
  + ' <div class="phone-main">'
2026
- + ' <div class="phone-num">' + human + ' <span style="font-size:11px;color:var(--text-dim);font-weight:400">from ' + fromHuman + '</span></div>'
2043
+ + ' <div class="phone-num">' + escapeHtml(human) + ' <span style="font-size:11px;color:var(--text-dim);font-weight:400">from ' + escapeHtml(fromHuman) + '</span></div>'
2027
2044
  + ' <div class="phone-meta">'
2028
- + ' <span class="chip ' + st.cls + '">' + st.label + '</span>'
2045
+ + ' <span class="chip ' + st.cls + '">' + escapeHtml(st.label) + '</span>'
2029
2046
  + ' <span class="chip">' + formatDuration(c.duration_sec) + '</span>'
2030
2047
  + ' <span class="chip">' + cost + '</span>'
2031
- + ' <span style="font-size:11px;color:var(--text-dim)">' + when + '</span>'
2048
+ + ' <span style="font-size:11px;color:var(--text-dim)">' + escapeHtml(when) + '</span>'
2032
2049
  + recordingHtml
2033
2050
  + ' </div>'
2034
2051
  + ' <div style="font-size:12px;color:var(--text-muted);margin-top:4px;line-height:1.5">' + (safeTask.slice(0, 200) + (safeTask.length > 200 ? '…' : '')) + '</div>'
@@ -2074,6 +2091,7 @@ document.querySelector('[data-tab="markets"]')?.addEventListener('click', loadMa
2074
2091
  const initialHash = (location.hash || '').replace(/^#/, '');
2075
2092
  if (initialHash && initialHash !== 'overview' && document.getElementById('tab-' + initialHash)) {
2076
2093
  activateTab(initialHash);
2094
+ if (initialHash === 'calls') loadCalls();
2077
2095
  }
2078
2096
  }
2079
2097
 
@@ -100,24 +100,30 @@ export class CallLog {
100
100
  summary(limit = 50) {
101
101
  const all = this.all();
102
102
  const latest = new Map();
103
+ const paidByCall = new Map();
103
104
  for (const e of all) {
105
+ paidByCall.set(e.call_id, Math.max(paidByCall.get(e.call_id) ?? 0, e.paid_usd));
104
106
  const cur = latest.get(e.call_id);
105
107
  // Keep the row with the FRESHEST timestamp per call_id (status updates).
106
108
  if (!cur || e.timestamp >= cur.timestamp)
107
109
  latest.set(e.call_id, e);
108
110
  }
109
111
  // Sort newest-first by the latest-row timestamp.
110
- const list = Array.from(latest.values()).sort((a, b) => b.timestamp - a.timestamp);
112
+ const list = Array.from(latest.values())
113
+ .map(e => ({ ...e, paid_usd: paidByCall.get(e.call_id) ?? e.paid_usd }))
114
+ .sort((a, b) => b.timestamp - a.timestamp);
111
115
  return list.slice(0, limit);
112
116
  }
113
117
  byCallId(callId) {
114
118
  let best = null;
119
+ let paidUsd = 0;
115
120
  for (const e of this.all()) {
116
121
  if (e.call_id !== callId)
117
122
  continue;
123
+ paidUsd = Math.max(paidUsd, e.paid_usd);
118
124
  if (!best || e.timestamp >= best.timestamp)
119
125
  best = e;
120
126
  }
121
- return best;
127
+ return best ? { ...best, paid_usd: paidUsd } : null;
122
128
  }
123
129
  }
@@ -346,7 +346,7 @@ export const voiceStatusCapability = {
346
346
  callLog().append({
347
347
  ...prior,
348
348
  timestamp: Date.now(),
349
- paid_usd: 0, // status polls are free; only the initial POST charges
349
+ paid_usd: prior.paid_usd, // status polls are free; preserve the per-call total
350
350
  status: normalizeStatus(res.status ?? res.queue_status ?? res.disposition),
351
351
  duration_sec: duration ?? prior.duration_sec,
352
352
  transcript: transcript ?? prior.transcript,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blockrun/franklin",
3
- "version": "3.21.2",
3
+ "version": "3.21.3",
4
4
  "description": "Franklin Agent — The AI agent with a wallet. Spends USDC autonomously to get real work done. Pay per action, no subscriptions.",
5
5
  "type": "module",
6
6
  "exports": {