troxy-cli 1.29.53 → 1.29.55
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 +1 -1
- package/src/daemon.js +1 -3
- package/src/init.js +4 -3
- package/src/interceptor.js +4 -2
- package/src/mods/troxy-guard/hooks/register.js +4 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "troxy-cli",
|
|
3
|
-
"version": "1.29.
|
|
3
|
+
"version": "1.29.55",
|
|
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": {
|
package/src/daemon.js
CHANGED
|
@@ -23,7 +23,7 @@ import { api } from './api.js';
|
|
|
23
23
|
import { createInterceptor } from './interceptor.js';
|
|
24
24
|
import { enabledProviders, interceptHostsFor, troxyRouteFor } from './providers.js';
|
|
25
25
|
import { ensureInterceptionCerts } from './tls-ca.js';
|
|
26
|
-
import { migrateBaseUrlProxyToInterception, claudeCodeSettingsPath
|
|
26
|
+
import { migrateBaseUrlProxyToInterception, claudeCodeSettingsPath } from './init.js';
|
|
27
27
|
|
|
28
28
|
const INTERVAL_MS = 60_000;
|
|
29
29
|
|
|
@@ -139,8 +139,6 @@ export async function runDaemon() {
|
|
|
139
139
|
process.on('unhandledRejection', (err) => {
|
|
140
140
|
process.stderr.write(`[troxy-daemon] unhandled rejection (continuing): ${err?.stack || err}\n`);
|
|
141
141
|
});
|
|
142
|
-
// A CLI upgrade (npm or troxy update) then a daemon restart brings the mod up to date.
|
|
143
|
-
refreshTroxyGuardMod().catch((err) => process.stderr.write(`[troxy-daemon] mod refresh failed: ${err.message}\n`));
|
|
144
142
|
|
|
145
143
|
const cfg = loadConfig();
|
|
146
144
|
if (!cfg?.apiKey) {
|
package/src/init.js
CHANGED
|
@@ -38,9 +38,10 @@ export async function installTroxyGuardMod(home = os.homedir(), detectFn = detec
|
|
|
38
38
|
return true;
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
-
// Only `troxy init` used to install the mod, so
|
|
42
|
-
//
|
|
43
|
-
// without the mod (never ran init, or uninstalled) is
|
|
41
|
+
// Only `troxy init` used to install the mod, so `troxy update` left the old mod running.
|
|
42
|
+
// Called from `troxy update` only: re-copies it when the installed one is stamped with a
|
|
43
|
+
// different CLI version. A machine without the mod (never ran init, or uninstalled) is
|
|
44
|
+
// left alone.
|
|
44
45
|
export async function refreshTroxyGuardMod(home = os.homedir(), detectFn = detectInstalledClaudeCodeVersion) {
|
|
45
46
|
const manifest = path.join(home, '.claude', 'skills', 'troxy-guard', '.claude-plugin', 'plugin.json');
|
|
46
47
|
let installed;
|
package/src/interceptor.js
CHANGED
|
@@ -118,9 +118,9 @@ function _readModRoute(dir, sessionId) {
|
|
|
118
118
|
const arm = n.arm === 'treated' || n.arm === 'holdout' ? n.arm : null;
|
|
119
119
|
const isFresh = Date.now() - n.ts < _MOD_ROUTE_MAX_AGE_MS;
|
|
120
120
|
const routed = isFresh && typeof n.from === 'string' && typeof n.to === 'string';
|
|
121
|
-
return { arm, from: routed ? n.from : null, to: routed ? n.to : null, compact: isFresh && n.compact === true };
|
|
121
|
+
return { arm, from: routed ? n.from : null, to: routed ? n.to : null, compact: isFresh && n.compact === true, cold: isFresh && typeof n.cold === 'boolean' ? n.cold : null };
|
|
122
122
|
} catch {
|
|
123
|
-
return { arm: null, from: null, to: null, compact: false };
|
|
123
|
+
return { arm: null, from: null, to: null, compact: false, cold: null };
|
|
124
124
|
}
|
|
125
125
|
}
|
|
126
126
|
|
|
@@ -183,6 +183,8 @@ export function buildTroxyForwardHeaders(incomingHeaders, troxyKey, sandboxState
|
|
|
183
183
|
if (modRoute && modRoute.arm) headers['x-troxy-mod-arm'] = modRoute.arm;
|
|
184
184
|
// The mod's idle compaction is in flight: this is its summary request.
|
|
185
185
|
if (modRoute && modRoute.compact) headers['x-troxy-mod-compact'] = '1';
|
|
186
|
+
// Tagged: whether this request hit the cold or warm path (for measurement).
|
|
187
|
+
if (modRoute && typeof modRoute.cold === 'boolean') headers['x-troxy-switch'] = modRoute.cold ? 'cold' : 'warm';
|
|
186
188
|
headers.authorization = `Bearer ${troxyKey}`;
|
|
187
189
|
return headers;
|
|
188
190
|
}
|
|
@@ -453,9 +453,10 @@ async function* routeStep($, e, next) {
|
|
|
453
453
|
const h = held.get(sid) || { decision: null, prompts: 0 };
|
|
454
454
|
const newPrompt = Boolean(decision) && decision !== h.decision;
|
|
455
455
|
if (newPrompt) h.decision = decision;
|
|
456
|
+
const isCold = !ls || now - ls.at >= WARM_MS || coldNext.has(sid);
|
|
456
457
|
const model = (await $.store.get('paused')) ? want : stepModel({
|
|
457
458
|
arm, requested: want, switched,
|
|
458
|
-
cold:
|
|
459
|
+
cold: isCold,
|
|
459
460
|
score: decision ? decision.score : null, warm: ls && ls.model,
|
|
460
461
|
newPrompt, heldPrompts: h.prompts, tokens: decision ? decision.tokens : Infinity,
|
|
461
462
|
});
|
|
@@ -470,7 +471,8 @@ async function* routeStep($, e, next) {
|
|
|
470
471
|
const key = `${sid}:${arm}:${want}>${model}`;
|
|
471
472
|
if (file && lastNote !== key) {
|
|
472
473
|
lastNote = key;
|
|
473
|
-
|
|
474
|
+
const route = model === want ? { arm, cold: isCold, ts: now } : { arm, from: want, to: model, cold: isCold, ts: now };
|
|
475
|
+
await $.fs.write(file, JSON.stringify(route));
|
|
474
476
|
}
|
|
475
477
|
try { await rememberAsked($, sid, want, model); } catch {} // a missed write only loses this on resume
|
|
476
478
|
if (model !== want && turn) turn.ratio = PRICE[tierOf(want)] / PRICE[tierOf(model)];
|