troxy-cli 1.30.11 → 1.30.13
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.30.
|
|
3
|
+
"version": "1.30.13",
|
|
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": {
|
|
@@ -74,7 +74,7 @@ const HAIKU_BANDS = [[0.2, 'haiku']];
|
|
|
74
74
|
// A Haiku session moves up to Sonnet, once and for good, when a later prompt scores above
|
|
75
75
|
// this or the context passes the Haiku price break (Haiku 5.5 is 5x dearer past 100k tokens).
|
|
76
76
|
// Never back down: every switch re-writes the cache.
|
|
77
|
-
const HAIKU_ESCALATE_ABOVE = 0.6
|
|
77
|
+
const HAIKU_ESCALATE_ABOVE = 0.3; // job B's bug and fix prompts scored 0.4 to 0.5 and Sonnet won them 8 to 2; at 0.6 they stayed on Haiku
|
|
78
78
|
const HAIKU_MAX_TOKENS = 100000;
|
|
79
79
|
const ROUTE_CONTEXT_FLOOR_TOKENS = 50000;
|
|
80
80
|
const SCORE_TIMEOUT_MS = 3000;
|
|
@@ -107,6 +107,23 @@ function rebuildWithText(toolResponse, text) {
|
|
|
107
107
|
return { ...toolResponse, content: text };
|
|
108
108
|
}
|
|
109
109
|
|
|
110
|
+
// Only outputs Claude rarely fetches back get shortened. Measured 2026-10-23 on 1,435 shortened
|
|
111
|
+
// outputs: Grep 0% recalled; wc, ls, head, tail, test runs and logs 0-15%; Read of .js/.html
|
|
112
|
+
// 17-20%. Targeted reads (sed, cat, Bash grep, Read of .py/.md/.txt) came back 30-86%, so
|
|
113
|
+
// shortening them only adds a recall step.
|
|
114
|
+
const SHORTEN_COMMANDS = new Set(['wc', 'ls', 'head', 'tail', 'ps', 'gh', 'aws', 'pytest']);
|
|
115
|
+
const SHORTEN_READ_EXT = /\.(js|html)$/i;
|
|
116
|
+
|
|
117
|
+
export function isShortenable(toolName, toolInput) {
|
|
118
|
+
if (toolName === undefined) return true; // ponytail: payloads without tool_name (old tests); Claude Code always sends it
|
|
119
|
+
if (toolName === 'Grep') return true;
|
|
120
|
+
if (toolName === 'Read') return SHORTEN_READ_EXT.test(String(toolInput?.file_path || ''));
|
|
121
|
+
if (toolName !== 'Bash') return false;
|
|
122
|
+
const cmd = String(toolInput?.command || '').trim().replace(/^(cd\s+\S+\s*(&&|;)\s*)+/, '');
|
|
123
|
+
if (/^python3?\s+-m\s+pytest\b/.test(cmd)) return true;
|
|
124
|
+
return SHORTEN_COMMANDS.has(path.basename(cmd.split(/\s+/)[0] || ''));
|
|
125
|
+
}
|
|
126
|
+
|
|
110
127
|
export async function buildHookResponse(toolUseBlock, {
|
|
111
128
|
sandboxFraction = 1,
|
|
112
129
|
holdoutFraction = 0.2,
|
|
@@ -124,7 +141,7 @@ export async function buildHookResponse(toolUseBlock, {
|
|
|
124
141
|
return undefined;
|
|
125
142
|
}
|
|
126
143
|
const output = extractText(toolResponse);
|
|
127
|
-
if (output === null || !shouldSandbox(output)) {
|
|
144
|
+
if (output === null || !shouldSandbox(output) || !isShortenable(toolUseBlock?.tool_name, toolUseBlock?.tool_input)) {
|
|
128
145
|
writeState(storeDir, 'skipped');
|
|
129
146
|
return undefined;
|
|
130
147
|
}
|
|
@@ -107,7 +107,8 @@ test('stepModel: a Haiku session moves up to Sonnet on a hard prompt or a long c
|
|
|
107
107
|
assert.equal(stepModel({ ...base, cold: true, score: 0.1, warm: null, tokens: 150000 }), MODELS.sonnet); // too long to start on Haiku
|
|
108
108
|
assert.equal(stepModel({ ...base, cold: false, score: 0.1, warm: MODELS.haiku }), MODELS.haiku); // easy prompt: hold
|
|
109
109
|
assert.equal(stepModel({ ...base, cold: false, score: null, warm: MODELS.haiku }), MODELS.haiku); // no score: hold
|
|
110
|
-
assert.equal(stepModel({ ...base, cold: false, score: 0.
|
|
110
|
+
assert.equal(stepModel({ ...base, cold: false, score: 0.4, warm: MODELS.haiku }), MODELS.sonnet); // harder prompt: up
|
|
111
|
+
assert.equal(stepModel({ ...base, cold: false, score: 0.3, warm: MODELS.haiku }), MODELS.haiku); // 0.3 or lower: hold
|
|
111
112
|
assert.equal(stepModel({ ...base, cold: false, score: 0.1, warm: MODELS.haiku, tokens: 120000 }), MODELS.sonnet); // long: up
|
|
112
113
|
assert.equal(stepModel({ ...base, cold: false, score: 0.1, warm: MODELS.sonnet }), MODELS.sonnet); // Sonnet never goes down
|
|
113
114
|
assert.equal(stepModel({ ...base, haiku: false, cold: false, score: 0.1, warm: MODELS.haiku }), MODELS.sonnet); // flag off: up
|
|
@@ -271,3 +271,25 @@ test('posttooluse-hook is a no-op unless TROXY_COMPRESS=1', () => {
|
|
|
271
271
|
fs.rmSync(tmpHome, { recursive: true, force: true });
|
|
272
272
|
}
|
|
273
273
|
});
|
|
274
|
+
|
|
275
|
+
test('isShortenable: only output kinds Claude rarely fetches back', async () => {
|
|
276
|
+
const { isShortenable } = await import('../sandbox/posttooluse-hook.js');
|
|
277
|
+
assert.equal(isShortenable('Grep', {}), true);
|
|
278
|
+
assert.equal(isShortenable('Read', { file_path: '/a/b/app.js' }), true);
|
|
279
|
+
assert.equal(isShortenable('Read', { file_path: '/a/b/notes.md' }), false);
|
|
280
|
+
assert.equal(isShortenable('Read', { file_path: '/a/b/x.py' }), false);
|
|
281
|
+
assert.equal(isShortenable('Bash', { command: 'cd /repo && ls -la src' }), true);
|
|
282
|
+
assert.equal(isShortenable('Bash', { command: 'python3 -m pytest -q' }), true);
|
|
283
|
+
assert.equal(isShortenable('Bash', { command: 'sed -n 1,200p file.js' }), false);
|
|
284
|
+
assert.equal(isShortenable('Bash', { command: 'cat big.log' }), false);
|
|
285
|
+
assert.equal(isShortenable('mcp__claude_ai_Gmail__get_thread', {}), false);
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
test('a large sed output passes through unshortened', async () => {
|
|
289
|
+
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'troxy-sandbox-'));
|
|
290
|
+
const response = await buildHookResponse(
|
|
291
|
+
{ tool_name: 'Bash', tool_input: { command: 'sed -n 1,400p a.js' }, tool_response: bashResult('x'.repeat(5000)) },
|
|
292
|
+
{ sandboxFraction: 1, holdoutFraction: 0, randomFn: () => 0.5, storeDir: dir });
|
|
293
|
+
assert.equal(response, undefined);
|
|
294
|
+
assert.equal(readState(dir).state, 'skipped');
|
|
295
|
+
});
|