🚀 Socket Launch Week Day 5:Introducing Repository Access Permissions and Custom Roles.Learn more
Sign In

agentlux

Package Overview
Dependencies
Maintainers
1
Versions
7
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

agentlux - npm Package Compare versions

Comparing version
1.0.4
to
1.0.5
+15
eslint.config.mjs
import js from '@eslint/js';
import globals from 'globals';
export default [
js.configs.recommended,
{
languageOptions: {
ecmaVersion: 2022,
globals: { ...globals.node }
},
rules: {
'no-unused-vars': ['error', { argsIgnorePattern: '^_' }]
}
}
];
+1
-0

@@ -20,2 +20,3 @@ name: CI

- run: npm ci
- run: npm run lint
- run: npm test

@@ -37,2 +37,5 @@ const fs = require('fs').promises;

function parseCropBox(raw) {
if (!raw || typeof raw !== 'object' || Array.isArray(raw)) {
throw new AgentLuxError('VLM_SCHEMA_ERROR', 'VLM response is not a valid JSON object.');
}
const requiredKeys = ['x', 'y', 'width', 'height'];

@@ -191,2 +194,5 @@ for (const key of requiredKeys) {

try {
if (typeof delete_after !== 'boolean') {
throw new AgentLuxError('INPUT_ERROR', 'delete_after must be a boolean.');
}
if (typeof image_path !== 'string' || image_path.trim().length === 0) {

@@ -193,0 +199,0 @@ throw new AgentLuxError('INPUT_ERROR', 'image_path must be a non-empty string.');

{
"name": "agentlux",
"version": "1.0.4",
"version": "1.0.5",
"description": "Zero-retention AgentSkill bringing the Leica 35mm aesthetic and Henri Cartier-Bresson's geometry to autonomous vision models.",
"main": "index.js",
"scripts": {
"lint": "eslint .",
"test": "node --test"

@@ -30,3 +31,8 @@ },

"sharp": "^0.33.2"
},
"devDependencies": {
"@eslint/js": "^10.0.1",
"eslint": "^10.1.0",
"globals": "^17.4.0"
}
}

@@ -116,5 +116,35 @@ # AgentLux QA Review Report

## Third-Pass Review Findings (round-2 closure)
### High
1. `parseCropBox` non-object input defense
- Evidence: VLM returning `null`, a primitive, or an array causes `in` operator to throw native `TypeError`, misclassified as `VLM_NETWORK_ERROR` (retryable).
- Resolution: added type guard `!raw || typeof raw !== 'object' || Array.isArray(raw)` → `VLM_SCHEMA_ERROR`.
- Status: fixed.
2. `delete_after` string-boolean misuse risk
- Evidence: passing `delete_after: "false"` is truthy, silently deletes source file.
- Resolution: added `typeof delete_after !== 'boolean'` check → `INPUT_ERROR`.
- Status: fixed.
### Medium
1. No lint/static-check gate in CI
- Evidence: CI had `npm test` only, no static analysis.
- Resolution: added ESLint (`@eslint/js` recommended + Node globals), `npm run lint` script, and CI lint step before test.
- Status: fixed.
2. Missing regression tests for delete_failed, timeout, 5xx retry
- Evidence: `delete_failed` branch, `VLM_TIMEOUT`, and 503/429 retry paths had zero test coverage.
- Resolution: added 5 tests: string delete_after rejection, delete_failed permission error, null crop defense, VLM_TIMEOUT on hung fetch, HTTP 503 retry-then-succeed.
- Status: fixed.
### Low (deferred)
1. Path allowlist/sandbox constraint
- Acknowledged residual risk; acceptable under current plug-and-play scope.
- Will add optional `AGENTLUX_ALLOWED_ROOT` in a future iteration.
## Test Evidence
- Command: `npm test`
- Result: 10 passed, 0 failed (5 original + 5 new)
- Command: `npm run lint && npm test`
- Lint: 0 errors, 0 warnings
- Tests: 15 passed, 0 failed

@@ -125,9 +155,10 @@ ## Priority Roadmap

- P1.5 (done): retry correctness + env validation + input validation tests + test isolation
- P2 (recommended next):
- add lightweight lint/check gate
- P2 (done): parseCropBox defense + delete_after type guard + ESLint gate + full regression coverage
- P3 (recommended next):
- add optional structured logs with request correlation
- add load tests for large-image throughput profile
- add optional `AGENTLUX_ALLOWED_ROOT` path sandbox
## Release Blockers
- None remaining for the scoped plan.
- None remaining.
- Recommended pre-release checks:

@@ -134,0 +165,0 @@ - verify `OPENAI_API_KEY` present in deployment env

@@ -220,1 +220,129 @@ const test = require('node:test');

});
test('rejects string delete_after to prevent truthy misuse', async () => {
setup();
const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), 'agentlux-'));
const imagePath = path.join(tmpDir, 'in.jpg');
await createFixtureImage(imagePath);
try {
const result = await agentlux.execute({ image_path: imagePath, delete_after: 'false' });
assert.equal(result.status, 'error');
assert.equal(result.error_code, 'INPUT_ERROR');
await fs.access(imagePath);
} finally {
teardown();
await fs.rm(tmpDir, { recursive: true, force: true });
}
});
// --- Deletion failure ---
test('delete_failed branch surfaces status and message', async () => {
setup();
const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), 'agentlux-'));
const imagePath = path.join(tmpDir, 'in.jpg');
await createFixtureImage(imagePath);
global.fetch = async () => mockOpenAIResponse({ x: 0, y: 0, width: 30, height: 30, rule: 'r' });
await fs.unlink(imagePath);
await createFixtureImage(imagePath);
await fs.chmod(tmpDir, 0o555);
try {
const result = await agentlux.execute({ image_path: imagePath, delete_after: true });
assert.equal(result.status, 'success');
assert.equal(result.source_file_deletion, 'delete_failed');
assert.equal(typeof result.source_file_deletion_message, 'string');
assert.ok(result.source_file_deletion_message.length > 0);
} finally {
await fs.chmod(tmpDir, 0o755);
teardown();
await fs.rm(tmpDir, { recursive: true, force: true });
}
});
// --- VLM null/array defense ---
test('VLM returning null is VLM_SCHEMA_ERROR without retry', async () => {
setup();
const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), 'agentlux-'));
const imagePath = path.join(tmpDir, 'in.jpg');
await createFixtureImage(imagePath);
let fetchCalls = 0;
global.fetch = async () => { fetchCalls += 1; return mockOpenAIResponse(null); };
try {
const result = await agentlux.execute({ image_path: imagePath, delete_after: false });
assert.equal(result.status, 'error');
assert.equal(result.error_code, 'VLM_SCHEMA_ERROR');
assert.equal(fetchCalls, 1, 'null crop must not trigger retries');
} finally {
teardown();
await fs.rm(tmpDir, { recursive: true, force: true });
}
});
// --- Timeout and 5xx retry ---
test('VLM_TIMEOUT on hung fetch', async () => {
setup();
process.env.AGENTLUX_VLM_TIMEOUT_MS = '100';
process.env.AGENTLUX_VLM_MAX_RETRIES = '0';
let mod;
try {
delete require.cache[require.resolve('../index.js')];
mod = require('../index.js');
} finally {
delete process.env.AGENTLUX_VLM_TIMEOUT_MS;
delete process.env.AGENTLUX_VLM_MAX_RETRIES;
}
const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), 'agentlux-'));
const imagePath = path.join(tmpDir, 'in.jpg');
await createFixtureImage(imagePath);
global.fetch = async (_url, opts) => {
await new Promise((_resolve, reject) => {
opts.signal.addEventListener('abort', () => reject(new DOMException('The operation was aborted.', 'AbortError')));
});
};
try {
const result = await mod.execute({ image_path: imagePath, delete_after: false });
assert.equal(result.status, 'error');
assert.equal(result.error_code, 'VLM_TIMEOUT');
} finally {
delete require.cache[require.resolve('../index.js')];
require('../index.js');
teardown();
await fs.rm(tmpDir, { recursive: true, force: true });
}
});
test('HTTP 503 retries then succeeds', async () => {
setup();
const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), 'agentlux-'));
const imagePath = path.join(tmpDir, 'in.jpg');
await createFixtureImage(imagePath);
let fetchCalls = 0;
global.fetch = async () => {
fetchCalls += 1;
if (fetchCalls === 1) {
return { ok: false, status: 503, statusText: 'Service Unavailable', text: async () => 'overloaded' };
}
return mockOpenAIResponse({ x: 5, y: 5, width: 40, height: 30, rule: '503 retry' });
};
try {
const result = await agentlux.execute({ image_path: imagePath, delete_after: false });
assert.equal(result.status, 'success');
assert.equal(fetchCalls, 2, '503 should be retried once then succeed');
} finally {
teardown();
await fs.rm(tmpDir, { recursive: true, force: true });
}
});