troxy-cli 1.29.65 → 1.29.66
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.66",
|
|
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": {
|
|
@@ -454,8 +454,15 @@ async function* routeStep($, e, next) {
|
|
|
454
454
|
if (e.agentId) return yield* next(e);
|
|
455
455
|
const sid = await $.session.id();
|
|
456
456
|
const now = await $.clock.now();
|
|
457
|
-
|
|
458
|
-
|
|
457
|
+
// The arm is kept on disk per session: $.state is per process, so every `claude -p` step,
|
|
458
|
+
// resume and restart re-rolled the 10% holdout and pinned most sessions to the requested model.
|
|
459
|
+
let arm = null;
|
|
460
|
+
try { arm = await $.store.get(`arm:${sid}`); } catch {}
|
|
461
|
+
if (arm !== 'holdout' && arm !== 'treated') {
|
|
462
|
+
arm = Math.random() < MOD_HOLDOUT_FRACTION ? 'holdout' : 'treated';
|
|
463
|
+
try { await $.store.set(`arm:${sid}`, arm); } catch {}
|
|
464
|
+
}
|
|
465
|
+
await $.state.set(ARM, arm);
|
|
459
466
|
const prev = lastAsked.get(sid);
|
|
460
467
|
lastAsked.set(sid, e.model);
|
|
461
468
|
const switched = Boolean(prev && prev !== e.model);
|
|
@@ -161,7 +161,7 @@ function routingApi() {
|
|
|
161
161
|
const sid = 'sess-' + Math.random().toString(36).slice(2, 10);
|
|
162
162
|
return {
|
|
163
163
|
state: { get: async (k) => ({ value: kv.get(k.key) }), set: async (k, v) => kv.set(k.key, v) },
|
|
164
|
-
store: { get: async () => undefined },
|
|
164
|
+
store: { get: async (k) => (String(k).startsWith('arm:') ? 'treated' : undefined) },
|
|
165
165
|
session: { id: async () => sid, usage: async () => ({ context: { tokens: 1000 }, rateLimits: [] }) },
|
|
166
166
|
plugin: { root: '/tmp/not-installed' },
|
|
167
167
|
model: { complete: async () => ({ isAnswered: true, text: '0.1' }) },
|
|
@@ -196,6 +196,7 @@ test('routeStep: a new process (claude -p step, resume, restart) keeps the warm
|
|
|
196
196
|
store: { get: async (k) => disk.get(k), set: async (k, v) => disk.set(k, v) },
|
|
197
197
|
clock: { now: async () => now },
|
|
198
198
|
model: { complete: async () => ({ isAnswered: true, text: score }) } };
|
|
199
|
+
disk.set(`arm:${await $.session.id()}`, 'treated'); // the arm is rolled once per session; pin it
|
|
199
200
|
assert.equal(await runTurn($, M.opus), M.sonnet); // cold start, easy prompt: sonnet
|
|
200
201
|
lastSent.clear(); score = '0.95'; // new process, hard prompt
|
|
201
202
|
now += 10 * 60 * 1000;
|
|
@@ -204,6 +205,23 @@ test('routeStep: a new process (claude -p step, resume, restart) keeps the warm
|
|
|
204
205
|
assert.equal(await runTurn($, M.opus), M.opus); // 55+ min idle: cold, re-picked
|
|
205
206
|
});
|
|
206
207
|
|
|
208
|
+
test('routeStep: the holdout arm is rolled once per session, not once per process', async () => {
|
|
209
|
+
const { MODELS: M, lastSent } = _internal;
|
|
210
|
+
const disk = new Map();
|
|
211
|
+
const mk = () => { const a = routingApi(); return { ...a,
|
|
212
|
+
state: { get: async () => ({ value: undefined }), set: async () => {} }, // per-process state starts empty
|
|
213
|
+
session: { ...a.session, id: async () => 'one-session' },
|
|
214
|
+
store: { get: async (k) => disk.get(k), set: async (k, v) => disk.set(k, v) } }; };
|
|
215
|
+
const real = Math.random;
|
|
216
|
+
try {
|
|
217
|
+
Math.random = () => 0.5; // would be treated
|
|
218
|
+
assert.equal(await runTurn(mk(), M.opus), M.sonnet);
|
|
219
|
+
lastSent.clear();
|
|
220
|
+
Math.random = () => 0.01; // a re-roll would now be a holdout
|
|
221
|
+
assert.equal(await runTurn(mk(), M.opus), M.sonnet); // new process: arm read from disk, still treated
|
|
222
|
+
} finally { Math.random = real; }
|
|
223
|
+
});
|
|
224
|
+
|
|
207
225
|
test('stepModel: decide only at cold moments, hold in between, holdout never routes', () => {
|
|
208
226
|
const { stepModel, MODELS: M } = _internal;
|
|
209
227
|
const base = { arm: 'treated', requested: M.opus, switched: false, warm: M.haiku };
|