troxy-cli 1.29.56 → 1.29.58
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.
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "troxy-cli",
|
|
3
|
-
"version": "1.29.
|
|
3
|
+
"version": "1.29.58",
|
|
4
4
|
"description": "A secure control layer for AI agents: policies across payments, messages, logins, destructive actions, model usage, and secrets, all enforceable from the CLI",
|
|
5
5
|
"homepage": "https://troxy.io",
|
|
6
6
|
"bugs": {
|
|
@@ -177,8 +177,10 @@ function savedLine(money, rateLimits = []) {
|
|
|
177
177
|
return `${bar(1 / scale)} ${parts.join(' · ')}`;
|
|
178
178
|
}
|
|
179
179
|
|
|
180
|
+
// t.kept: what the content Troxy cleared or compressed would have cost (server's
|
|
181
|
+
// session-savings kept_usd), on top of the model swaps in t.full.
|
|
180
182
|
function sessionLine(t) {
|
|
181
|
-
return t && savedLine({ cost_paid_usd: t.paid, dollars_saved_usd: t.full - t.paid },
|
|
183
|
+
return t && savedLine({ cost_paid_usd: t.paid, dollars_saved_usd: t.full - t.paid + (t.kept || 0) },
|
|
182
184
|
Object.entries(t.meter).map(([kind, percentUsed]) => ({ kind, percentUsed })));
|
|
183
185
|
}
|
|
184
186
|
|
|
@@ -247,7 +249,7 @@ function addTurn(totals, start, end, ratio) {
|
|
|
247
249
|
for (const k of Object.keys(end.meter)) {
|
|
248
250
|
if (k in start.meter) meter[k] = (meter[k] || 0) + Math.max(0, end.meter[k] - start.meter[k]);
|
|
249
251
|
}
|
|
250
|
-
return { paid: t.paid + paid, full: t.full + paid * ratio, meter };
|
|
252
|
+
return { ...t, paid: t.paid + paid, full: t.full + paid * ratio, meter }; // keeps t.kept
|
|
251
253
|
}
|
|
252
254
|
|
|
253
255
|
// Idle compaction: shrink a big session just before its prompt cache expires (1h), so
|
|
@@ -528,21 +530,25 @@ export function register(on) {
|
|
|
528
530
|
// After the answer, so counting never delays a prompt.
|
|
529
531
|
on('turn.complete', async ($, e, next) => {
|
|
530
532
|
const r = await next(e);
|
|
533
|
+
let savings = null;
|
|
534
|
+
try {
|
|
535
|
+
savings = await fetchSavings($);
|
|
536
|
+
if (savings) await $.state.set(SAVINGS, savingsText(savings));
|
|
537
|
+
} catch {} // a missed fetch only leaves the band as it was
|
|
531
538
|
if (turn) {
|
|
532
539
|
const { start, ratio } = turn;
|
|
533
540
|
turn = null;
|
|
534
541
|
try {
|
|
535
542
|
const { value } = await $.state.get(SAVED);
|
|
536
543
|
const totals = addTurn(value, start, await snapshot($), ratio);
|
|
544
|
+
// ponytail: kept_usd is the whole session's server total, paid only counts turns
|
|
545
|
+
// since the mod started tracking; close enough once the session has a few turns.
|
|
546
|
+
if (savings && savings.kept_usd >= 0) totals.kept = savings.kept_usd;
|
|
537
547
|
await $.state.set(SAVED, totals);
|
|
538
548
|
const line = sessionLine(totals);
|
|
539
549
|
if (line) await $.state.set(LINE, line);
|
|
540
550
|
} catch {} // a missed turn only leaves the band as it was
|
|
541
551
|
}
|
|
542
|
-
try {
|
|
543
|
-
const text = savingsText(await fetchSavings($));
|
|
544
|
-
if (text) await $.state.set(SAVINGS, text);
|
|
545
|
-
} catch {} // a missed fetch only leaves the band as it was
|
|
546
552
|
let stayAwake = false;
|
|
547
553
|
try { stayAwake = await bigSession($); } catch {}
|
|
548
554
|
armCompact($, COMPACT_IDLE_MS, { stayAwake, persist: true });
|
|
@@ -88,6 +88,10 @@ test('addTurn: a session total from its own turns, a routed one counted at the a
|
|
|
88
88
|
t = addTurn(t, { cost: 1.5, meter: { five_hour: 13, seven_day: 5 } }, { cost: 2, meter: { five_hour: 1, seven_day: 6 } }, 1);
|
|
89
89
|
assert.deepEqual(t, { paid: 1, full: 1.5, meter: { five_hour: 3, seven_day: 2 } });
|
|
90
90
|
assert.equal(sessionLine(t), '[███████░░░] This session: your bar lasts 1.5x longer · 5h: 3 instead of 5 · 7d: 2 instead of 3 · estimate');
|
|
91
|
+
// Cleared/compressed content counts too, and survives the next turn: $1 paid, $0.5 swap + $1.5 kept = 3x.
|
|
92
|
+
t = addTurn({ ...t, kept: 1.5 }, { cost: 2, meter: {} }, { cost: 2, meter: {} }, 1);
|
|
93
|
+
assert.equal(t.kept, 1.5);
|
|
94
|
+
assert.match(sessionLine(t), /your bar lasts 3\.0x longer · 5h: 3 instead of 9/);
|
|
91
95
|
// No routed turn yet: no band.
|
|
92
96
|
assert.equal(sessionLine(addTurn(undefined, { cost: 0, meter: {} }, { cost: 1, meter: {} }, 1)), undefined);
|
|
93
97
|
});
|
package/src/tls-ca.js
CHANGED
|
@@ -187,11 +187,11 @@ export function ensureInterceptionCerts(dir, { hostname, leafDnsNames, leafRenew
|
|
|
187
187
|
: null;
|
|
188
188
|
const leafStillHealthy = existingLeaf?.certPem
|
|
189
189
|
&& certIsValid(existingLeaf.certPem)
|
|
190
|
-
&& !certExpiresWithin(existingLeaf.certPem, leafRenewWithinDays)
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
190
|
+
&& !certExpiresWithin(existingLeaf.certPem, leafRenewWithinDays);
|
|
191
|
+
// Skipped: forge.pki.certificateFromPem(caCertPem).verify(existingLeaf.certPem)
|
|
192
|
+
// node-forge RSA signature verification is vulnerable (GHSA-86w9-cpqp-85rv, unfixed).
|
|
193
|
+
// For this local cert: CA and leaf are both generated by Troxy, stored in secret files.
|
|
194
|
+
// If the CA changes, TLS will fail on next use and the user restarts. Low risk.
|
|
195
195
|
if (leafStillHealthy) {
|
|
196
196
|
({ certPem: leafCertPem, keyPem: leafKeyPem } = existingLeaf);
|
|
197
197
|
} else {
|