Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,6 @@ jobs:
- run: npm ci # prepare script builds the web UI (and regenerates public/js/pure.js)
# public/js/pure.js is generated from frontend/src/lib/{pure,markdown}.ts;
# fail when the committed copy has drifted from the TS sources.
- run: git diff --exit-code public/js/pure.js
- run: git diff --exit-code public/js/pure.js lib/generated/diagnostics.cjs
- run: npx biome check .
- run: npm test
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,8 @@ Rule of thumb: if you build and operate your own agent in production, use a trac

## Features

- **Offline evidence CLI** — `agentxray inspect --platform codex session.jsonl --json` reads one explicitly selected log without a server or model. Versioned, minimized reports expose source lines and shared UI-rule hashes; opt-in pending-failure gates never claim task correctness. Supports Codex, OMP and Claude Code JSONL. [Automation contract](docs/offline-inspect.md).

- **Automatic session health** — Opens with factual failure, repetition, follow-up and last-recorded call-state summaries. Missing/running/unknown results have evidence links; no human labels or model calls required. Manual notes and transfers are opt-in and never hide automatic facts. [Scope and offline checks](docs/diagnostics.md#automatic-session-health).
- **Codex background-process evidence** — Connect explicit `exec_command` process IDs to later `write_stdin` results, with launch/poll/exit source links. Ambiguous IDs or polling sequences stay unknown; process completion never rewrites historical tool-call states or proves a task passed. [Association limits](docs/diagnostics.md#codex-background-process-evidence).
- **Modification/check chronology** — Distinguish checks before an edit, checks overlapping it and later outcomes. A passed earlier check or a successful output pipeline is not post-change validation; ambiguous command fragments remain unknown. [Recognition and coverage limits](docs/diagnostics.md#modification-and-verification-chronology).
Expand Down Expand Up @@ -322,7 +324,7 @@ Archived sessions (`.jsonl.reset.*`, `.jsonl.deleted.*`) are shown for OpenClaw

## Development

Tests live in `test/` and use Node's built-in test runner — no extra dependencies. Run `npm ci` once, then `npm test` (`node --test test/*.test.js`). The tests start their own server on a random port with `HOME` and every platform directory pointed at a throwaway copy of `test/fixtures/home`, so your real session logs are never read or modified. CI (`.github/workflows/test.yml`) runs on Node 22 for every push and pull request to `master`, in four steps: `npm ci` (whose `prepare` script builds the web UI and regenerates `public/js/pure.js`), a drift check (`git diff --exit-code public/js/pure.js`), `npx biome check .`, and `npm test`.
Tests live in `test/` and use Node's built-in test runner — no extra dependencies. Run `npm ci` once, then `npm test` (`node --test test/*.test.js`). The tests start their own server on a random port with `HOME` and every platform directory pointed at a throwaway copy of `test/fixtures/home`, so your real session logs are never read or modified. CI (`.github/workflows/test.yml`) runs on Node 22 for every push and pull request to `master`, in four steps: `npm ci` (whose `prepare` script builds the web UI and regenerates `public/js/pure.js`), a drift check (`git diff --exit-code public/js/pure.js lib/generated/diagnostics.cjs`), `npx biome check .`, and `npm test`.

**Adding a platform** takes two files: write one adapter in `lib/platforms/<name>.js` (list / find / parse / normalize for that log format — `lib/platforms/shared.js` provides the metadata cache, the normalized-message factory and the session sort), then register it in the `PLATFORMS` table in `lib/platforms/index.js`. The generic session routes, search, watch (SSE tail), insights, prompts, tool audit, OTLP and Markdown/HTML export all resolve platforms through that registry — no other file needs to change.

Expand Down
2 changes: 2 additions & 0 deletions README.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@ LangSmith、Langfuse 这类观测平台面向的是*你自己写的* agent:接

## 功能特性

- **离线证据 CLI** — `agentxray inspect --platform codex session.jsonl --json` 无需启动服务或调用模型,输出带输入/规则哈希和原始行号的结构化报告,供 Agent、脚本和 CI 使用。只读明确指定的 Codex、OMP、Claude Code JSONL;默认不输出日志正文和参数,门禁须显式开启。[自动化契约](docs/offline-inspect.md)。

- **自动会话体检** — 默认自动整理失败、重复操作、后续候选和调用最后记录状态;执行中、未知及未记录结果可追溯证据。不依赖人工标注或模型调用,笔记与迁移改为可选,不影响自动事实展示。[口径与离线验证](docs/diagnostics.md#自动体检无需人工标注)。
- **Codex 后台进程证据** — 用明确进程 ID 关联启动、`write_stdin` 轮询和退出结果,可逐步跳转原始证据。ID 重用、轮询交叠和冲突保持未知;不改写历史工具状态,不把进程退出当作任务通过。[关联边界](docs/diagnostics.md#codex-后台进程证据)。
- **修改—检查时序** — 区分修改前成功的检查、与修改重叠的检查及后续最新结果;不把先前通过或管道整体成功当作修改后的验证。复杂命令片段执行状态保持未知。[识别边界](docs/diagnostics.md#修改与验证的先后顺序)。
Expand Down
51 changes: 28 additions & 23 deletions bin/agentxray.js
Original file line number Diff line number Diff line change
@@ -1,30 +1,35 @@
#!/usr/bin/env node
// CLI entry: parse --port/--host, export them, then boot the server.
const argv = process.argv.slice(2);
let port = process.env.PORT;
let host = process.env.HOST;
if (argv[0] === 'inspect') {
void require('./inspect').main(argv.slice(1));
} else {
let port = process.env.PORT;
let host = process.env.HOST;

for (let i = 0; i < argv.length; i++) {
const arg = argv[i];
const eq = arg.indexOf('=');
const flag = eq === -1 ? arg : arg.slice(0, eq);
const inline = eq === -1 ? null : arg.slice(eq + 1);
const next = () => (inline !== null ? inline : argv[++i]);
if (flag === '--port' || flag === '-p') port = next();
else if (flag === '--host' || flag === '-H') host = next();
else if (flag === '--version' || flag === '-v') {
console.log(require('../package.json').version);
process.exit(0);
} else if (flag === '--help' || flag === '-h') {
console.log('Usage: agentxray [--port <port>] [--host <host>] [--version]');
process.exit(0);
} else {
console.error(`agentxray: unknown option '${arg}'`);
process.exit(1);
for (let i = 0; i < argv.length; i++) {
const arg = argv[i];
const eq = arg.indexOf('=');
const flag = eq === -1 ? arg : arg.slice(0, eq);
const inline = eq === -1 ? null : arg.slice(eq + 1);
const next = () => (inline !== null ? inline : argv[++i]);
if (flag === '--port' || flag === '-p') port = next();
else if (flag === '--host' || flag === '-H') host = next();
else if (flag === '--version' || flag === '-v') {
console.log(require('../package.json').version);
process.exit(0);
} else if (flag === '--help' || flag === '-h') {
console.log('Usage: agentxray [--port <port>] [--host <host>] [--version]');
console.log('Offline evidence: agentxray inspect --help');
process.exit(0);
} else {
console.error(`agentxray: unknown option '${arg}'`);
process.exit(1);
}
}
}

if (port) process.env.PORT = String(port);
if (host) process.env.HOST = String(host);
if (port) process.env.PORT = String(port);
if (host) process.env.HOST = String(host);

require('../server.js');
require('../server.js');
}
77 changes: 77 additions & 0 deletions bin/inspect.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
const HELP = `Usage: agentxray inspect --platform <omp|codex|claude-code> <file.jsonl> [--json] [--fail-on pending-failures]

Read one stable regular UTF-8 JSONL file (maximum 64 MiB), without starting a server.
Reports omit raw logs, paths, commands and IDs; source references are one-based lines/positions.
Exit 0: complete report, NOT task success. Exit 1: input/runtime/coverage error.
Exit 2: pending failure records found, only when --fail-on pending-failures is requested.
`;

async function main(args) {
let filename,
platform,
json = false,
policy;
const seen = new Set();
let positionalOnly = false;
try {
if (args.length === 1 && ['--help', '-h'].includes(args[0])) {
process.stdout.write(HELP);
return;
}
for (let index = 0; index < args.length; index++) {
const arg = args[index];
if (!positionalOnly && arg === '--') {
positionalOnly = true;
continue;
}
if (!positionalOnly && arg.startsWith('-')) {
const [flag, ...inline] = arg.split('=');
if (!['--platform', '--json', '--fail-on'].includes(flag) || seen.has(flag))
throw new Error('Invalid or duplicate option.');
seen.add(flag);
if (flag === '--json') {
if (inline.length) throw new Error('--json does not take a value.');
json = true;
continue;
}
const value = inline.length ? inline.join('=') : args[++index];
if (!value || value.startsWith('-')) throw new Error('Missing option value.');
if (flag === '--platform') platform = value;
else policy = value;
} else {
if (filename !== undefined) throw new Error('Provide exactly one input file.');
filename = arg;
}
}
if (!filename || !platform) throw new Error('Explicit --platform and one input file are required.');
if (policy !== undefined && policy !== 'pending-failures')
throw new Error('Supported --fail-on policy: pending-failures.');
} catch (error) {
process.stderr.write(`agentxray inspect: ${error.message}\n${HELP}`);
process.exitCode = 1;
return;
}
let implementation;
try {
implementation = require('../lib/inspect');
} catch {
process.stderr.write('agentxray inspect: bundled rules unavailable; rebuild or reinstall the package.\n');
process.exitCode = 1;
return;
}
const { inspectFile, renderText, InspectError } = implementation;
try {
const report = await inspectFile(filename, platform);
process.stdout.write(json ? `${JSON.stringify(report, null, 2)}\n` : renderText(report));
process.exitCode = !report.complete ? 1 : policy && report.summary.pendingRecords ? 2 : 0;
if (!report.complete)
process.stderr.write('agentxray inspect: adapter coverage is incomplete; see report.coverage.issues.\n');
} catch (error) {
process.stderr.write(
`agentxray inspect: ${error instanceof InspectError ? error.message : 'Inspection failed; no report generated.'}\n`
);
process.exitCode = 1;
}
}

module.exports = { main };
11 changes: 10 additions & 1 deletion biome.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,16 @@
{
"$schema": "./node_modules/@biomejs/biome/configuration_schema.json",
"files": {
"includes": ["server.js", "bin/**", "lib/**", "scripts/**", "public/js/**", "test/**/*.js", "!public/js/pure.js"]
"includes": [
"server.js",
"bin/**",
"lib/**",
"scripts/**",
"public/js/**",
"test/**/*.js",
"!public/js/pure.js",
"!lib/generated"
]
},
"formatter": {
"enabled": true,
Expand Down
14 changes: 7 additions & 7 deletions claims.json
Original file line number Diff line number Diff line change
Expand Up @@ -102,16 +102,16 @@
},
{
"id": "test-count",
"claim": "313 tests pass on Node's built-in test runner, the count docs/ROADMAP.md records for `npm test`.",
"value": "313",
"metric": "passing node:test cases (# tests 313 / # pass 313 / # fail 0)",
"method": "npm test → node --test test/*.test.js, run in the claims job after npm ci, and the TAP summary is asserted. The roadmap sentence ('313 tests on Node's built-in runner (`npm test`, 2026-09-23)') is verified by the run, not read back from the prose.",
"claim": "332 tests pass on Node's built-in test runner, the count docs/ROADMAP.md records for `npm test`.",
"value": "332",
"metric": "passing node:test cases (# tests 332 / # pass 332 / # fail 0)",
"method": "npm test → node --test test/*.test.js, run in the claims job after npm ci, and the TAP summary is asserted. The roadmap sentence ('332 tests on Node's built-in runner (`npm test`, 2026-09-23)') is verified by the run, not read back from the prose.",
"repro": "npm test 2>&1 | grep -E '^# (tests|pass|fail)'",
"evidence": "docs/ROADMAP.md",
"as_of": "2026-09-13",
"check": {
"cmd": "npm test 2>&1 | grep -E '^# (tests|pass|fail)'",
"expect": { "contains": ["# tests 313", "# pass 313", "# fail 0"] },
"expect": { "contains": ["# tests 332", "# pass 332", "# fail 0"] },
"timeout": 120
}
},
Expand All @@ -127,7 +127,7 @@
"check": {
"cmd": "node scripts/claims-receipts.mjs ci-test-workflow",
"expect": {
"equals": "1 job (test) on Node 22 · 4 steps: npm ci | git diff --exit-code public/js/pure.js | npx biome check . | npm test · on push, pull_request of master"
"equals": "1 job (test) on Node 22 · 4 steps: npm ci | git diff --exit-code public/js/pure.js lib/generated/diagnostics.cjs | npx biome check . | npm test · on push, pull_request of master"
},
"timeout": 60
}
Expand Down Expand Up @@ -238,7 +238,7 @@
"check": {
"cmd": "node scripts/claims-receipts.mjs tests-node-only",
"expect": {
"equals": "21 files in test/ · 15 distinct requires: 11 node builtins, 4 relative, 0 third-party"
"equals": "22 files in test/ · 16 distinct requires: 11 node builtins, 5 relative, 0 third-party"
},
"timeout": 60
}
Expand Down
6 changes: 5 additions & 1 deletion docs/ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
- **Session browser** with tool-call inspection, trace/waterfall view, spawn tracking and message timeline
- **Prompt tooling** — extraction (noise filtered), template clustering with outcome attribution, Claude-powered rewrites, and a prompt library that installs entries as native slash commands
- **Global search** across all platforms, insights dashboard, incremental session backup
- **React + Vite frontend** served by an Express backend; 313 tests on Node's built-in runner (`npm test`, 2026-09-23), CI on Node 22
- **React + Vite frontend** served by an Express backend; 332 tests on Node's built-in runner (`npm test`, 2026-09-23), CI on Node 22
- **Evidence-backed failure events and local review** with full-result invalidation, evidence navigation and narrow-screen session layout

## Current priorities
Expand All @@ -24,6 +24,10 @@ External grounding: official guidance emphasizes [executable verification](https
| P2 | Execution/verification evidence, not another statistics dashboard | Automatic health and modification/check chronology distinguish before/overlap/after and unknown outcomes. Codex process IDs now connect launch/poll/exit evidence without rewriting history or original check start. Next investigate explicit task/child linkage and per-step outcomes; reject ambiguous associations rather than relaxing shell assumptions. Deterministic tests and frozen-log checks are not file coverage, human time saved or universal accuracy. |
| P3 | Make releases reproducible for contributors | Keep clean-install tests, generated fixtures, documentation claims and release/package verification aligned. Add browser regression automation when it can run deterministically without personal logs. |

### Machine-consumable evidence

The next integration surface is a read-only offline CLI, not a new agent runtime or hosted service. `inspect` emits versioned facts and source references using the same generated rules as the UI. This lets an agent or CI step consume evidence without manual tagging, a browser or model scoring. Explicit exit policies and coverage failures must never become a generic "task passed" claim. Current scope is one stable Codex/OMP/Claude Code JSONL file; logs remain on the machine.

No launch dates or star-count targets are promised. Progress is gated on these observable outcomes. Physical-device/keyboard coverage and complex Trace/analytics layouts remain separate work, not implied by the session-screen checks.

## Existing backlog
Expand Down
14 changes: 14 additions & 0 deletions docs/diagnostics-verification.md
Original file line number Diff line number Diff line change
Expand Up @@ -130,3 +130,17 @@ The frozen real corpus was re-read under content hashes: all previous `diagnoseS
Synthetic browser checks validate the process summary, unlinked poll count, source-result navigation, a test launched before an edit remaining overlapping, a later failed check remaining visible and live completion changing terminal process count from 2 to 3 without changing historical failure events. At 390px the process panel has no horizontal overflow. `node scripts/demo-process-evidence.cjs` reproduces the example locally without executing transcript commands.

Unknown/conflicting chains are not silently certified; callers who need live job control or cross-session task association still need stronger runtime evidence. Input is represented as a boolean in the process summary, though original call evidence remains accessible. Existing large-bundle and lint findings remain unchanged.

## Offline inspect acceptance

`agentxray inspect --platform <omp|codex|claude-code> <file> --json` adds a machine-consumable surface without a web server. The CommonJS rules are generated from the UI's TypeScript source, packaged under `lib/generated/`, compared against source in tests and checked for committed drift in CI. Historical UI diagnostic rules are unchanged.

The full suite now has **332 passing tests**, including **19 inspect tests**. Tests verify deterministic JSON, exact source references, minimized outputs, policy exits, malformed/truncated input, invalid UTF-8, directories/missing/oversized files, wrong platform, changed-read metadata, missing flags, blank/CRLF physical lines and generated-rule parity. A runtime guard blocks Express/http/https/net/server imports while the CLI runs; input bytes and temporary HOME entries remain unchanged.

Known Claude multi-result and text/result mixed records are tested as incomplete adapter coverage: JSON contains `complete:false`, raw/normalized counts and issue lines, and exit status is 1 even when a pending-failure gate was requested. This exposes an existing parser limitation instead of claiming it is fixed. Parsing failures output no partial report or sensitive line text.

An actual npm tarball was unpacked to an isolated directory with no `node_modules`, frontend source or TypeScript compiler. Inspect produced the expected OMP synthetic report (7 pending records, 2 events) directly from that artifact. This tests standalone inspection, not the dashboard dependency requirements.

The previously frozen 15 real sessions / 2,024 tool results were read only in memory. CLI report summaries matched UI rules exactly: 142 failures, 141 pending records, 76 events and 60 recorded Codex launches. **2,493 source references** were verified against the frozen original line/message mapping; repeated reports were byte-identical. No real raw text, commands, paths, process identifiers or human notes were emitted into public artifacts. This is rule/report parity, not real-world accuracy or evidence of time saved.

Reports omit raw content by construction, but hashes, counts and associations may still be sensitive. The output is not anonymized, signed task proof or an autonomous safety decision. Exit 0 means valid report production; only the explicitly requested `pending-failures` policy returns 2. See [the offline contract](offline-inspect.md).
Loading
Loading