troxy-cli 1.29.52 → 1.29.54
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 +3 -1
- package/src/init.js +13 -0
- package/src/interceptor.js +4 -2
- package/src/mods/troxy-guard/hooks/register.js +4 -2
- package/src/tests/init-mods.test.js +18 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "troxy-cli",
|
|
3
|
-
"version": "1.29.
|
|
3
|
+
"version": "1.29.54",
|
|
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 } from './init.js';
|
|
26
|
+
import { migrateBaseUrlProxyToInterception, claudeCodeSettingsPath, refreshTroxyGuardMod } from './init.js';
|
|
27
27
|
|
|
28
28
|
const INTERVAL_MS = 60_000;
|
|
29
29
|
|
|
@@ -139,6 +139,8 @@ 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`));
|
|
142
144
|
|
|
143
145
|
const cfg = loadConfig();
|
|
144
146
|
if (!cfg?.apiKey) {
|
package/src/init.js
CHANGED
|
@@ -38,6 +38,18 @@ 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 a CLI upgrade left the old mod running.
|
|
42
|
+
// Re-copy it when the installed one is stamped with a different CLI version; a machine
|
|
43
|
+
// without the mod (never ran init, or uninstalled) is left alone.
|
|
44
|
+
export async function refreshTroxyGuardMod(home = os.homedir(), detectFn = detectInstalledClaudeCodeVersion) {
|
|
45
|
+
const manifest = path.join(home, '.claude', 'skills', 'troxy-guard', '.claude-plugin', 'plugin.json');
|
|
46
|
+
let installed;
|
|
47
|
+
try { installed = JSON.parse(fs.readFileSync(manifest, 'utf8')).version; } catch { return false; }
|
|
48
|
+
const { version } = JSON.parse(fs.readFileSync(path.join(__dirname, '..', 'package.json'), 'utf8'));
|
|
49
|
+
if (installed === version) return false;
|
|
50
|
+
return installTroxyGuardMod(home, detectFn);
|
|
51
|
+
}
|
|
52
|
+
|
|
41
53
|
export function uninstallTroxyGuardMod(home = os.homedir()) {
|
|
42
54
|
fs.rmSync(path.join(home, '.claude', 'skills', 'troxy-guard'), { recursive: true, force: true });
|
|
43
55
|
}
|
|
@@ -113,6 +125,7 @@ export async function refreshHostedInstructions(key) {
|
|
|
113
125
|
|
|
114
126
|
export async function reprovisionKeyConsumers(key, agentName, proxyOptIn = null, conciseOptIn = null) {
|
|
115
127
|
const platform = process.platform;
|
|
128
|
+
try { if (await refreshTroxyGuardMod()) console.log(' • Troxy model routing updated ✓'); } catch {}
|
|
116
129
|
const { detected, hasOpenClaw, hasClaude, none } = detectMcpClients();
|
|
117
130
|
|
|
118
131
|
if (none) {
|
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)];
|
|
@@ -3,7 +3,7 @@ import assert from 'node:assert/strict';
|
|
|
3
3
|
import os from 'node:os';
|
|
4
4
|
import path from 'node:path';
|
|
5
5
|
import fs from 'node:fs';
|
|
6
|
-
import { installTroxyGuardMod, uninstallTroxyGuardMod } from '../init.js';
|
|
6
|
+
import { installTroxyGuardMod, uninstallTroxyGuardMod, refreshTroxyGuardMod } from '../init.js';
|
|
7
7
|
|
|
8
8
|
test('installTroxyGuardMod: copies the mod when the version supports it', async () => {
|
|
9
9
|
const tmpHome = fs.mkdtempSync(path.join(os.tmpdir(), 'troxy-mod-home-'));
|
|
@@ -77,3 +77,20 @@ test('uninstallTroxyGuardMod: a no-op (not a throw) when nothing was installed',
|
|
|
77
77
|
fs.rmSync(tmpHome, { recursive: true, force: true });
|
|
78
78
|
}
|
|
79
79
|
});
|
|
80
|
+
|
|
81
|
+
test('refreshTroxyGuardMod: re-copies a stale mod, leaves a current or missing one alone', async () => {
|
|
82
|
+
const tmpHome = fs.mkdtempSync(path.join(os.tmpdir(), 'troxy-mod-home-'));
|
|
83
|
+
const cc = async () => ({ major: 2, minor: 1, patch: 289 });
|
|
84
|
+
const manifest = path.join(tmpHome, '.claude', 'skills', 'troxy-guard', '.claude-plugin', 'plugin.json');
|
|
85
|
+
try {
|
|
86
|
+
assert.equal(await refreshTroxyGuardMod(tmpHome, cc), false); // never installed: stays off
|
|
87
|
+
assert.equal(fs.existsSync(manifest), false);
|
|
88
|
+
await installTroxyGuardMod(tmpHome, cc);
|
|
89
|
+
assert.equal(await refreshTroxyGuardMod(tmpHome, cc), false); // already current
|
|
90
|
+
fs.writeFileSync(manifest, JSON.stringify({ name: 'troxy-guard', version: '0.0.1' }));
|
|
91
|
+
assert.equal(await refreshTroxyGuardMod(tmpHome, cc), true); // stale: re-copied and re-stamped
|
|
92
|
+
assert.notEqual(JSON.parse(fs.readFileSync(manifest, 'utf8')).version, '0.0.1');
|
|
93
|
+
} finally {
|
|
94
|
+
fs.rmSync(tmpHome, { recursive: true, force: true });
|
|
95
|
+
}
|
|
96
|
+
});
|