diff --git a/.gitignore b/.gitignore index 6a22b25..dbcf1ba 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,6 @@ node_modules/ frontend/node_modules/ frontend/dist/ + +output/ +.playwright-cli/ diff --git a/README.md b/README.md index 379638b..6b62520 100644 --- a/README.md +++ b/README.md @@ -88,6 +88,9 @@ Rule of thumb: if you build and operate your own agent in production, use a trac - **Prompt library** — Curate the prompts worth keeping into `~/.agentxray/library`, tag / edit / search them, then install any of them as a native slash command for Claude Code, Codex or OMP with one click — `$ARGUMENTS` is passed through, so `/name some args` works in the target CLI - **Global search** — One search box across all seven platforms at once, multi-keyword AND matching, colored platform badges per hit — including prompts recovered from sessions that Claude Code's cleanup already deleted - **Session insights** — Aggregate analytics dashboard with tool stats, error clustering and daily trends +- **Evidence-backed failure events (React UI)** — Groups unresolved failures by the same tool, complete arguments and call's user turn, with repeated operations first, first/last evidence jumps and every original result retained. Successful results split groups; missing arguments stay separate. Execution success requires an explicit zero exit code or OMP-native completion evidence. These are review groups, not root-cause diagnoses or proof of task failure. Local rules, no LLM. [Try the synthetic walkthrough and read the boundaries](docs/diagnostics.md). +- **Local review queue** — Record follow-up, expected-failure or alternative-verification notes in your browser. Evidence changes invalidate the old review; manual labels never rewrite automatic outcomes. No account or review backend. [Review workflow and storage limits](docs/diagnostics.md#local-review-workflow). +- **Narrow-screen session workflow** — Below 768px, switch between the session list and full-width content without losing the current review draft; platform tabs scroll horizontally, and evidence jumps keep navigation visible. Desktop retains the two-column layout. [Scope and tested viewports](docs/diagnostics.md#narrow-screen-session-workflow). - **Spawn tracking** — Detect and navigate parent/child agent relationships - **OMP sub-agents** — Sub-agents spawned by an OMP session show up as chips in the summary; click one to read the child agent's full transcript - **Message timeline** — Visual graph showing conversation flow with role indicators diff --git a/README.zh-CN.md b/README.zh-CN.md index da738d5..df07385 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -54,6 +54,10 @@ LangSmith、Langfuse 这类观测平台面向的是*你自己写的* agent:接 ## 功能特性 +- **有证据的失败事件(React UI)** — 将同一调用所在用户轮次、同工具、完整同参数的待复查失败分组,重复最多的操作优先展示;可跳转首末及每条原始证据。同参成功切断分组,缺少参数不合并。执行成功采用明确零退出码或 OMP 原生完成证据;事件不等于根因或任务失败。本地规则,无 LLM。[合成演示与判定边界](docs/diagnostics.md#中文使用指南)。 +- **本机复核队列** — 用必填依据标记“需跟进”“预期失败”“其他验证已通过”,仅存当前浏览器;新证据使旧标记失效,人工判断不改写自动结果。无需账号或复核后端。[使用方式与存储边界](docs/diagnostics.md#本机复核闭环)。 +- **窄屏会话复核** — 小于 768px 时切换“会话列表 / 返回内容”,正文获得完整宽度,切换列表不清空当前复核草稿;平台栏横向滚动,证据跳转保留顶部导航,桌面继续双栏。[验收范围](docs/diagnostics.md#窄屏操作)。 + - **多平台支持** — 一个界面统一查看 OpenClaw、Codex、Claude Code、Hermes、OMP、DeepSeek Harness、Gemini CLI 的会话日志(dsh 的多帧 zstd 压缩日志透明解压;Gemini CLI 的 `/rewind` 回滚记录会先折叠,回滚掉的历史不会重复渲染) - **会话浏览** — 浏览 Agent 列表,搜索/过滤会话,查看消息历史 - **工具调用检查** — 可展开的工具调用详情,包含参数和返回结果 diff --git a/claims.json b/claims.json index aae51f9..8052821 100644 --- a/claims.json +++ b/claims.json @@ -1,8 +1,8 @@ { - "$comment": "Receipts for every number AgentXRay publishes in prose: the README first screen, the hero figure caption, the FAQ, the roadmap and the CI description. Each claim carries the command that recomputes it from committed artifacts (scripts/claims-receipts.mjs derives the figures from the platform registry, lib/config.js, package.json, the workflow, the demo sample log and the test fixtures) or check.manual with the reason no command can. No claim asserts a package version: package.json is 1.17.2 while npm still serves 1.17.1 and this machine has no npm token, so a version-consistency claim would be permanently red; instead readme-no-version pins the deliberate absence of a version in the README. .github/workflows/claims.yml runs the lot weekly and on every push.", + "$comment": "Receipts for every number AgentXRay publishes in prose: the README first screen, the hero figure caption, the FAQ, the roadmap and the CI description. Each claim carries the command that recomputes it from committed artifacts (scripts/claims-receipts.mjs derives the figures from the platform registry, lib/config.js, package.json, the workflow, the demo sample log and the test fixtures) or check.manual with the reason no command can. The readme-no-version receipt keeps version-specific installation details in release notes rather than the README. .github/workflows/claims.yml runs the lot weekly and on every push.", "project": "AgentXRay", "repository": "https://github.com/alloevil/AgentXRay", - "updated": "2026-09-13", + "updated": "2026-09-23", "claims": [ { "id": "platform-registry", @@ -102,16 +102,16 @@ }, { "id": "test-count", - "claim": "130 tests pass on Node's built-in test runner, the count docs/ROADMAP.md records for `npm test`.", - "value": "130", - "metric": "passing node:test cases (# tests 130 / # pass 130 / # 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 ('130 tests on Node's built-in runner (`npm test`, 2026-09-12)') is verified by the run, not read back from the prose.", + "claim": "214 tests pass on Node's built-in test runner, the count docs/ROADMAP.md records for `npm test`.", + "value": "214", + "metric": "passing node:test cases (# tests 214 / # pass 214 / # 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 ('214 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 130", "# pass 130", "# fail 0"] }, + "expect": { "contains": ["# tests 214", "# pass 214", "# fail 0"] }, "timeout": 120 } }, @@ -238,7 +238,7 @@ "check": { "cmd": "node scripts/claims-receipts.mjs tests-node-only", "expect": { - "equals": "12 files in test/ · 11 distinct requires: 9 node builtins, 2 relative, 0 third-party" + "equals": "16 files in test/ · 14 distinct requires: 11 node builtins, 3 relative, 0 third-party" }, "timeout": 60 } @@ -248,7 +248,7 @@ "claim": "The README publishes no version number of its own — the npm release badge is the only version shown — so no committed prose can go stale against the registry.", "value": "0 version strings", "metric": "semver-looking strings in README.md, excluding the 127.0.0.1 in the host example", - "method": "README.md is scanned for standalone x.y.z tokens (not part of an IP address). package.json's own version is deliberately not claimed anywhere: it is 1.17.2 while npm still serves 1.17.1, and there is no npm token on this machine to reconcile the two, so a version-consistency claim would be permanently red.", + "method": "README.md is scanned for standalone x.y.z tokens (not part of an IP address). Version-specific installation instructions belong to release notes; this receipt does not assert registry availability.", "repro": "node scripts/claims-receipts.mjs readme-no-version", "evidence": "README.md", "as_of": "2026-09-13", @@ -263,8 +263,8 @@ "claim": "master requires the `test` check to pass before a pull request can merge, so the workflow above is a gate and not just a notification.", "value": "required check: test", "metric": "the required status checks on the master branch", - "method": "Branch protection is GitHub repository state, not a committed file: no command in this repository can read it, and this machine has no token that could query the protection API (the same absence that makes the published-version comparison manual). The committed half — that test.yml defines exactly one job, named test — is machine-checked by ci-test-workflow.", - "repro": "gh api repos/alloevil/AgentXRay/branches/master/protection --jq '.required_status_checks.contexts'", + "method": "Branch protection and rulesets are GitHub repository state, not committed files; inspect them with an authenticated GitHub API request. The committed half — that test.yml defines exactly one job, named test — is machine-checked by ci-test-workflow.", + "repro": "gh api repos/alloevil/AgentXRay/rulesets (then inspect the active default-branch ruleset by ID)", "evidence": ".github/workflows/test.yml", "as_of": "2026-09-13", "check": { diff --git a/docs/ROADMAP.md b/docs/ROADMAP.md index 7fb7ffb..f3c6d11 100644 --- a/docs/ROADMAP.md +++ b/docs/ROADMAP.md @@ -2,13 +2,14 @@ > Moved from issue #4 on 2026-09-04. Issues are for bug reports and feature requests; the roadmap lives here. To pick up an item, open an issue referencing it. -## Where we are (v1.12) +## Where we are -- **5 platforms** — OpenClaw, Codex, Claude Code, Hermes (SQLite) and OMP, parsed from their native log formats +- **7 platforms** — OpenClaw, Codex, Claude Code, Hermes (SQLite), OMP, DeepSeek Harness and Gemini CLI, parsed from their native log formats - **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; 130 tests on Node's built-in runner (`npm test`, 2026-09-12), CI on Node 22 +- **React + Vite frontend** served by an Express backend; 214 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 ## Roadmap diff --git a/docs/diagnostics-verification.md b/docs/diagnostics-verification.md new file mode 100644 index 0000000..aaed906 --- /dev/null +++ b/docs/diagnostics-verification.md @@ -0,0 +1,59 @@ +# Diagnostics verification receipt + +This receipt accompanies the evidence-backed diagnostics, local review queue and narrow-screen session workflow. All public screenshots and demo fixtures are synthetic. Personal session logs, sample manifests, review notes and detailed local evaluation artifacts are not distributed. + +## Reproduce from a checkout + +With Node 22.13+ and the project dependencies installed: + +```sh +npm test +npm run build:ui +npm run lint +git diff --exit-code -- public/js/pure.js +node scripts/demo-diagnostics.cjs +``` + +Open the printed URL and select the OMP session marked `[Synthetic]`: + +- Initially 72 unresolved failure records form 7 events, with 66 repeated edit failures in the first event. One earlier failure already has a matching success. +- First/last/all-evidence actions retain every original result, including results outside message pagination. +- Save a human review note. Refresh retains the note; another session cannot inherit it. Human status never changes automatic failure counts. +- Enter `n` in the terminal to append a synthetic failure. The repeated event now contains 67 failures and its previous review becomes stale. +- Enter `r` to append matching success. Six events and six unresolved records remain, with 68 automatically recovered failures (67 if `n` was not used). +- Enter `q` or Ctrl-C to stop the isolated server and clean up its temporary HOME. + +## Recorded checks (2026-09-23) + +| Check | Result | +| --- | --- | +| Node test suite | 214 passed, 0 failed, 0 skipped | +| Diagnostics/events/OMP/review focused suites | 84 passed | +| UI build | TypeScript and Vite passed | +| Lint | Exit 0; existing 91 warnings / 159 informational findings | +| Legacy generated bundle | No drift | +| Review persistence | Save, refresh, session isolation and cross-tab updates passed | +| Review invalidation | New failure/full-result changes reopen review; stale notes remain distinguishable | +| Storage failure | Read denial, corrupt records and write failure visibly reported; failed save never claims success | +| Evidence navigation | First/middle/last results retained; pagination expands and target is shown | +| Narrow screens | 360×640, 390×844, 740×360, 768×1024 and 1440×1000 checked | +| Touch simulation | Chromium mobile/touch taps navigate and save review | +| Virtual session list | 240 synthetic sessions; initially 16 rendered cards, final item reachable | + +At 360px viewport width, the real session diagnostic panel measures 340px (the old fixed-sidebar layout left 44px). At 740×360, 75px remains for transcript scrolling; portrait orientation is more comfortable. On mobile and desktop, evidence jumps retain `window.scrollY=0` when starting at the top. + +The preceding private, already-inspected regression set had 30 sessions and 9,076 tool results. Its 438 unresolved records grouped into 373 events while retaining all evidence; 373 in-memory synthetic review labels were checked for isolation and invalidation, then discarded. These aggregate counts are contextual observations, **not a public benchmark, independent human judgment, accuracy measure or proof of time saved**. The private dataset is deliberately not included; the commands above reproduce the public synthetic acceptance tests. + +## Screenshots + +![Synthetic event grouping](../screenshots/diagnostic-events.png) +![Synthetic human review](../screenshots/diagnostic-reviews.png) +![Synthetic narrow-screen review](../screenshots/diagnostic-mobile.png) + +## Limits + +- Human notes are unencrypted, browser-local and origin-specific; they are not authenticated evidence or a backup. +- Alternate commands, implicit working directories, background-task chains and semantic equivalence are not inferred. +- Existing aggregate statistics retain their original error-field semantics and may differ from diagnostic counts. +- No physical iOS/Android device, Safari or software-keyboard certification is claimed. Other complex pages are not fully covered by the narrow-screen checks. +- Existing lint findings and the Vite large-chunk warning remain; no claim of zero technical debt is made. diff --git a/docs/diagnostics.md b/docs/diagnostics.md new file mode 100644 index 0000000..226f9a5 --- /dev/null +++ b/docs/diagnostics.md @@ -0,0 +1,152 @@ +# Evidence-backed failure events + +**Find repeated failed operations without losing the evidence.** AgentXRay groups recorded failures for review; it does not decide whether the agent finished your task correctly. + +This guide covers the React UI. The synthetic terminal walkthrough uses a source checkout; package installation and published versions are listed in GitHub Releases. + +## Try it without sharing your logs + +From the repository root, after installing the root and frontend dependencies: + +```sh +npm run build:ui +node scripts/demo-diagnostics.cjs +``` + +Open the printed local address, select **OMP**, then the **[Synthetic]** session. The script creates a temporary HOME with synthetic logs, not your real sessions. It executes no logged tool commands and needs no model credentials. + +1. **Start with the summary:** 72 pending failure records become 7 events. The first card contains 66 failures of the same edit operation. Five event cards appear initially; “再显示…” reveals the rest. +2. **Follow the evidence:** “首条证据” / “末条证据” jump to the first / last result. “全部 66 条证据” retains every result, including different error text, with individual jump buttons. A jump expands the original tool result, including results outside message pagination. +3. **Watch the update:** keep Auto-refresh enabled, enter `r` in the demo terminal, then press Enter. The script appends a synthetic same-argument success. The repeated event disappears: 6 pending records / 6 events remain, and 67 failed records now have later matching success evidence (including one earlier synthetic recovery). +4. Enter `q` or press Ctrl-C to stop the isolated server and remove its temporary data. + +The synthetic “66 → 1” example is a fixture, not an accuracy or productivity benchmark. + +![Synthetic failure-event walkthrough](../screenshots/diagnostic-events.png) + +## Use your existing logs + +Run `npm start` from the built checkout. Select a session and open its **消息** view. The “失败后验证” panel uses the currently loaded transcript; selecting a child session analyzes the child, not the parent's combined history. No SDK, new log instrumentation or model call is required for these rules. + +Start with repeated operations, open the latest failure, and inspect the surrounding calls. A card is a reason to review the transcript—not an instruction to rerun a possibly destructive command. Equivalent commands, alternate verification and external fixes still require your judgment. + +## Local review workflow + +The automatic result and your judgment are separate. The header always reports automatic events and pending failure records. The review filters show which of those events you have inspected: + +| Queue | Meaning | +| --- | --- | +| 待复核 / Unreviewed | No current review, evidence changed, or local review could not be read. | +| 需跟进 / Follow-up | You recorded a concrete next check or unresolved concern. | +| 预期失败 / Expected failure | You recorded why this failure was expected, such as a negative probe. | +| 其他验证已通过 / Verified elsewhere | You recorded alternative verification that the exact-argument rule cannot establish. This is your note, not an automatic success claim. | +| 全部 / All | All automatic events, including manually reviewed ones, with their original evidence. | + +Click **记录人工复核**, choose a status and write **1–1000 characters of evidence or next steps**. Save, change or revoke a review without modifying session logs. Start with **待复核** next time; **需跟进** is your follow-up list. An empty review queue does not imply a passed task. + +Notes survive refresh in the same browser and origin, and updates sync between tabs on that origin. Notes are separated by platform, configured log directory, session, child and event. Event identifiers and full-evidence revisions are SHA-256 fingerprints; the app does not copy raw logs or arguments into the review store. Your note itself is stored as plain text, so **do not paste secrets**. + +Appending a failure or changing the full result—including text beyond the displayed preview—invalidates the old revision. The event returns to **待复核**, and where its identity is unchanged the old note remains visible with an expiration warning. Changed event identities (for example changed parameters or shifted first occurrence) start unreviewed rather than reusing a potentially unrelated note. + +**Try invalidation:** in the synthetic demo, save a review on the 66-failure event, then enter `n` in the terminal. A new failure joins the event (67 failures), and the old review becomes stale without a page reload. Enter `r` afterward to append a matching success: that event leaves the automatic queue, with 68 recorded failures now having matching success evidence. If you did not use `n`, the original demo count remains 67 recovered records. + +![Synthetic local review with explicit evidence](../screenshots/diagnostic-reviews.png) + +Storage limits: + +- Reviews stay in this browser's localStorage, not on the AgentXRay server, in your session logs or in the cloud. No review API, account or model call is used. +- They are not encrypted, authenticated attestations or a backup. Another browser, a changed hostname/port, private mode or cleared site data may make them unavailable. Other tabs use the latest saved record; there is no collaborative edit merge. +- Storage denial, a corrupt record or quota exhaustion is shown explicitly. A failed save does not count as saved; unreadable reviews remain unreviewed. You can revoke an individual review without clearing unrelated settings. +- Use localhost or HTTPS for browser cryptography. If fingerprints cannot be calculated, saving reviews is disabled while automatic diagnostics remain available. +- Notes are not automatically deleted when an event becomes automatically recovered or its identity changes; they may remain in site storage. There is no review archive, export/import or cross-device sync in this version. + +## What an event means + +| Rule | Behavior | +| --- | --- | +| Same operation | Exact tool name and complete argument values; object key order is ignored, array order and values are preserved. OMP's `i` field is not stripped. | +| User-turn boundary | Calls must originate in the same user turn. A late result stays in its call's turn. No user message yet is shown explicitly. | +| Success boundary | A matching successful result separates later groups. Only a call started after the failure can recover that failure; pre-existing parallel calls cannot. Recovery itself is not restricted to one user turn. | +| Missing data | Missing calls/arguments stay separate. Unknown completion does not silently become success. | +| Evidence | Each pending failure belongs to exactly one event. First, last and every member remain accessible. Ranking is by member count, then first occurrence. | +| Time | The card reports the interval between first and last failure records, not execution duration or time wasted. Missing, invalid or non-monotonic timestamps produce unknown duration. | +| No causal claim | Errors may differ and calls may overlap. Grouping does not prove a common cause, serial retries, an infinite loop, deception or task failure. | + +OMP results distinguish success, failure, running, cancelled and unknown with field-source evidence. Eval/search nested errors are recognized; stopping a daemon or listing historical failures is not automatically a new execution failure. A successful eval containing error observations keeps those observations visible: process success does not prove the probe or task passed. + +Existing aggregate error statistics still use their original `isError` field. They may differ from this diagnostic count; this feature does not migrate those metrics. + +## Narrow-screen session workflow + +Below 768px, the session workflow uses the available width rather than reserving a fixed sidebar: + +- Tap **会话列表** to open navigation. Select a session—even the currently selected one—to return to its content. **返回内容** or Escape also closes navigation; keyboard focus returns to the toggle. +- Session-list search and the current review form remain mounted while switching between list and content. This preserves an unsaved draft **within the same session**; it does not promise draft persistence across session changes or page reloads. +- Scroll the platform bar horizontally to reach more platforms. The active platform stays visible; desktop displays the existing two-column layout at 768px and above. +- The summary has a height limit on narrow screens, including short landscape viewports. Expanded details scroll inside the summary, leaving space for the transcript. +- First/last/individual evidence jumps scroll the message area, not the outer page, so navigation remains available. Review notes and stale-evidence handling work as on desktop. + +Validated in Chromium at 360×640, 390×844, 740×360, 768×1024 and 1440×1000. A separate mobile/touch emulation passed tap navigation and review saving. At 360px, the actual diagnostic panel is **340px wide**, up from the previous broken 44px layout. This replaces the earlier component-only narrow-screen check. + +![Narrow-screen synthetic review](../screenshots/diagnostic-mobile.png) + +These checks cover navigation, session messages and the review workflow—not complete mobile certification of Trace, analytics, prompts, library or every settings control. No physical iOS/Android device or on-screen keyboard was tested. This UI change does not alter server network binding or expose your logs for remote access. Review storage still requires a suitable browser origin (localhost or HTTPS for fingerprints). [Verification receipt](diagnostics-verification.md). + +## Reproducible checks + +```sh +node --test test/diagnostic-events.test.js test/diagnostics.test.js test/omp-outcome.test.js +node --test test/diagnostic-reviews.test.js +npm test +npm run build:ui +npm run lint +``` + +The local frozen regression set contained 30 sessions and 9,076 tool results. Grouping reduced 438 pending cards to **373 events (14.84% fewer cards)**, retaining all 438 evidence records. One event contains 66 edit failures. Failure detection and recovery counts did not change. This already-inspected set is not a blind evaluation, an accuracy estimate or evidence of time saved. Real logs are not included in the repository; the synthetic demo works without them. [Local verification receipt](diagnostics-verification.md). + +## 中文使用指南 + +**目标:先找到值得复查的重复操作,再追溯证据,而不是把几百条失败强行解释成几个根因。** + +从源码仓库运行 `npm run build:ui`,再运行 `node scripts/demo-diagnostics.cjs`。打开终端打印的地址,选择 OMP 的 `[Synthetic]` 会话: + +- 初始为 **72 条待复查记录 → 7 个事件**,66 条同参 edit 失败集中在第一张卡片;默认展示 5 个事件,可以继续加载。 +- 点击“首条证据”“末条证据”或“全部 66 条证据”,可以跳转任意一条原始结果,不因聚合丢失细节。 +- 保持自动刷新,在终端输入 `r` 并回车,追加合成成功结果;页面自动变为 **6 个事件、6 条待复查记录、67 条已有同参成功记录**。 +- 输入 `q` 或 Ctrl-C 退出并清理。演示使用临时 HOME 和合成日志,不读取你的真实会话、不执行日志里的命令。 + +日常使用:构建后运行 `npm start`,选择你的会话,在“消息”视图查看“失败后验证”。子会话独立分析;优先读最新失败和前后操作,不要不加判断地重跑原命令。 + +**边界:**只合并同调用轮次、同工具、完整同参数的记录,不忽略 `i` 或工作目录;成功结果切断分组,缺少参数不合并。事件可含并行调用和不同错误,不等于同一根因或串行重试。首末间隔不是耗时或浪费时间。执行中、取消和未知不算成功验证;其他方式修复、等价命令、后台任务链仍需人工判断。 + +本机冻结回归是 **438 → 373 个事件,少 14.84% 卡片,保留全部 438 条证据**,不是“问题减少了 14.84%”,也没有证明省下多少时间。既有聚合统计仍使用原始错误标记,可能与诊断数字不同。源码体验与安装方法见本页及 GitHub Releases;合成终端演示需要源码检出。 + +会话与复核流程的窄屏布局现已修复;此前 360px 下固定侧栏挤压正文的问题不再存在,具体范围见“窄屏操作”。其他复杂页面没有因此被宣称全面适配。 + +## 本机复核闭环 + +先从“待复核”开始,打开事件证据,点击“记录人工复核”,选择: + +- **需跟进**:写清楚下一步检查什么、哪里仍不确定。 +- **预期失败**:写清楚为什么这个失败是合理的,例如负向探测无匹配。 +- **其他验证已通过**:记录替代验证的命令、结果与时间;这是人工判断,不是自动成功证据。 + +每次必须填写 1–1000 字依据。可重新复核、撤销单条标记,或在“全部”中查看所有自动事件。**复核队列为空,不代表任务通过**;人工标记不会改变顶部的自动失败/恢复计数。 + +在合成演示里,先给 66 条失败的事件写一条依据,再在终端输入 `n`:第 67 条失败加入后,旧标记失效、事件重新待复核,并保留旧依据供对照。此后输入 `r`,自动成功记录才会关闭这一事件(累计 68 条恢复,包含先前演示中的 1 条)。 + +**存储边界:**标记只在当前浏览器同源 localStorage 中保存,刷新可恢复、同源多标签页会同步,但不上传后端、不改原始日志。自动保存的内容为哈希标识、完整证据指纹、状态、时间和你手写的依据,不复制原始日志或参数;手写依据是明文,请勿填写密钥。不同日志目录、平台、会话及子会话隔离。换浏览器、端口、清除站点数据可能不可见或丢失,不能当备份;多标签页采用最后一次保存,不做协作合并。 + +新增失败、完整输出变化或状态依据变化会要求重新复核;身份变化的事件不沿用旧标记。读取损坏、浏览器拒绝存储或配额不足会明确报错,不会假装保存成功。使用 localhost 或 HTTPS 以便计算指纹。自动恢复或身份变化后的旧笔记不会自动清理;当前没有复核归档、导入导出或跨设备同步。 + +本轮的 373 个真实冻结事件只使用**内存中的合成测试标记**检验隔离与失效,没有替你判断真实事件,也没有把这些测试标记写成真实复核。完整证据见 [本机复核验收](diagnostics-verification.md)。 + +## 窄屏操作 + +- 小于 768px 时,点击“会话列表”进入导航,选择当前或其他会话后返回正文;“返回内容”或 Escape 也可关闭列表。桌面仍是双栏。 +- 在同一会话内,打开列表再返回不会清空搜索词或未保存的复核草稿;切换其他会话、刷新页面不保证未保存草稿。 +- 平台栏支持横向滚动,当前平台自动保持可见;会话列表的设置和全局搜索入口仍可使用。 +- 摘要在窄屏/短横屏限制高度,展开详情后可内部滚动,不再把消息区挤没。证据跳转只滚动消息区,顶部导航保持可用。 +- 360px 下诊断区由 **44px → 340px**;360×640、390×844、740×360、768×1024、1440×1000 布局及 Chromium 触屏模拟通过。另验证了 240 个合成会话的虚拟列表、长会话 ID/路径和新增失败使复核过期。 + +这是浏览器窄屏与触屏模拟验收,不是 iPhone/Android 真机或软键盘兼容性认证;Trace、统计、Prompts、资产库及完整设置流程不在本轮全面适配范围。没有改变服务监听地址或自动向网络暴露日志。[本轮证据](diagnostics-verification.md)。 diff --git a/docs/releases/v1.18.0.md b/docs/releases/v1.18.0.md new file mode 100644 index 0000000..87715c4 --- /dev/null +++ b/docs/releases/v1.18.0.md @@ -0,0 +1,29 @@ +# v1.18.0 — Evidence-backed session review + +AgentXRay now helps you work through recorded failures, not just browse a transcript. No SDK, account or model call is required for diagnostics or human review. + +## What's new + +- **Failure events:** group unresolved failures by the same tool, complete arguments and call's user turn; prioritize repeated operations and keep first, last and every individual evidence jump. +- **OMP result semantics:** identify nested eval/search failures and distinguish successful completion, running work, cancellation and unknown results without rewriting raw fields. +- **Local review queue:** save required evidence notes for follow-up, expected failures or alternative verification. Notes survive refresh in the same browser, stay isolated by session and become stale when full evidence changes. +- **Narrow-screen session workflow:** switch between the session list and content below 768px; preserve same-session drafts, scroll platform navigation and keep page navigation visible during evidence jumps. +- **Synthetic walkthrough:** run `node scripts/demo-diagnostics.cjs` from a checkout to try repeated failures, review invalidation and matching success without sharing personal logs. + +## Validation + +214 Node tests passed; UI build and lint passed with existing lint findings unchanged. Browser checks cover evidence navigation, storage failures, cross-tab/session isolation, live updates, five viewport sizes and mobile/touch emulation. See [the public verification receipt](../diagnostics-verification.md). + +## Important boundaries + +Diagnostics describe loaded records, not task correctness or a shared root cause. A manually reviewed event is not automatically recovered. Notes are unencrypted browser-local storage, not a backup; changing browser/origin or clearing site data affects availability. Real-device and full mobile coverage of all views are not claimed. + +No new runtime dependencies, log uploads or automatic execution of logged commands. Local evaluation artifacts and personal session data are excluded from Git and npm packaging. + +## Install + +```sh +npx @alloevil/agent-xray@1.18.0 +``` + +Requires Node.js 22.13+ (compressed DeepSeek Harness logs require 22.15+). diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 6f9d899..b8b6c2e 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -1,4 +1,5 @@ -import { Copy } from 'lucide-react'; +import { ArrowLeft, Copy, PanelLeft } from 'lucide-react'; +import { useCallback, useEffect, useRef, useState } from 'react'; import { toast } from 'sonner'; import { PlatformBar } from '@/components/PlatformBar'; import { Sidebar } from '@/components/Sidebar'; @@ -12,6 +13,7 @@ import { InsightsView } from '@/views/insights/InsightsView'; import { LibraryView } from '@/views/library/LibraryView'; import { PromptsView } from '@/views/prompts/PromptsView'; import { SessionsView } from '@/views/sessions/SessionsView'; +import { cn } from '@/lib/utils'; const TABS: { view: MainView; label: string; title: string }[] = [ { view: 'sessions', label: '会话', title: '浏览与回放会话:消息、工具调用、耗时与 token' }, @@ -24,12 +26,50 @@ export default function App() { useVersionPoller(); const view = useAppStore((s) => s.view); const setView = useAppStore((s) => s.setView); + const [navigationOpen, setNavigationOpen] = useState(false); + const navigationToggle = useRef(null); + const navigationPanel = useRef(null); + const returnToContent = useCallback(() => { + setNavigationOpen(false); + if (window.matchMedia('(max-width: 767px)').matches) { + requestAnimationFrame(() => navigationToggle.current?.focus()); + } + }, []); + + useEffect(() => { + if (navigationOpen && window.matchMedia('(max-width: 767px)').matches) { + navigationPanel.current?.querySelector('input[type="search"]')?.focus(); + } + }, [navigationOpen]); + + useEffect(() => { + const media = window.matchMedia('(min-width: 768px)'); + const onResize = () => { + setNavigationOpen(false); + if (!media.matches && navigationPanel.current?.contains(document.activeElement)) { + requestAnimationFrame(() => navigationToggle.current?.focus()); + } + }; + const onKeyDown = (event: KeyboardEvent) => { + if (event.key === 'Escape' && !media.matches && navigationOpen && + !(event.target as HTMLElement).closest('[role="dialog"]')) { + event.preventDefault(); + returnToContent(); + } + }; + media.addEventListener('change', onResize); + document.addEventListener('keydown', onKeyDown); + return () => { + media.removeEventListener('change', onResize); + document.removeEventListener('keydown', onKeyDown); + }; + }, [navigationOpen, returnToContent]); return (
{DEMO ? ( -
+
🧪 Demo mode — synthetic sample data (not real user sessions). Inspect your own agent logs: +
+
+
+ +
+
setView(v as MainView)} className="flex min-h-0 flex-1 flex-col" > -
- +
+ {TABS.map((tab) => ( {tab.label} ))}
- + - + - + - + diff --git a/frontend/src/api/types.ts b/frontend/src/api/types.ts index 4e4f0a5..5b4ea18 100644 --- a/frontend/src/api/types.ts +++ b/frontend/src/api/types.ts @@ -73,6 +73,12 @@ export interface MessageUsage { } /** Normalized message (all platforms are normalized server-side to this shape). */ +export interface OmpOutcome { + state: 'success' | 'failure' | 'running' | 'cancelled' | 'unknown'; + evidence: string[]; + warnings: string[]; +} + export interface SessionMessage { id: string; timestamp: string | null; @@ -85,6 +91,7 @@ export interface SessionMessage { toolName: string | null; details: Record | null; isError: boolean; + ompOutcome?: OmpOutcome; /** some platforms attach reasoning text directly on the assistant record */ reasoning?: string | null; [key: string]: unknown; diff --git a/frontend/src/components/PlatformBar.tsx b/frontend/src/components/PlatformBar.tsx index 889389b..e286820 100644 --- a/frontend/src/components/PlatformBar.tsx +++ b/frontend/src/components/PlatformBar.tsx @@ -1,4 +1,4 @@ -import { useEffect, useState } from 'react'; +import { useEffect, useRef, useState } from 'react'; import type { Platform } from '@/api/types'; import { PLATFORM_LABELS, PLATFORMS } from '@/api/types'; import { DEMO } from '@/demo/flag'; @@ -22,6 +22,18 @@ export function PlatformBar() { const setPlatform = useAppStore((s) => s.setPlatform); const [expanded, setExpanded] = useState(false); const { data: counts } = usePlatformProbe(); + const barRef = useRef(null); + + useEffect(() => { + const bar = barRef.current; + const selected = bar?.querySelector('[aria-pressed="true"]'); + if (!bar || !selected) return; + const bounds = bar.getBoundingClientRect(); + const button = selected.getBoundingClientRect(); + if (button.left < bounds.left || button.right > bounds.right) { + bar.scrollLeft += button.left - bounds.left - (bar.clientWidth - button.width) / 2; + } + }, [platform, counts, expanded]); // First-launch auto-pick (#13): no platform persisted yet → land on the // first platform that actually has sessions. SessionList then auto-selects @@ -39,15 +51,16 @@ export function PlatformBar() { const collapsed = PLATFORMS.filter((p) => isCollapsed(p)); return ( -
+
+ ); } diff --git a/frontend/src/components/Sidebar.tsx b/frontend/src/components/Sidebar.tsx index 6458fac..d585bd1 100644 --- a/frontend/src/components/Sidebar.tsx +++ b/frontend/src/components/Sidebar.tsx @@ -79,7 +79,7 @@ function AgentNav() { ); } -export function Sidebar() { +export function Sidebar({ onNavigate }: { onNavigate?: () => void }) { const platform = useAppStore((s) => s.platform); const includeArchived = useAppStore((s) => s.includeArchived); const setIncludeArchived = useAppStore((s) => s.setIncludeArchived); @@ -106,7 +106,7 @@ export function Sidebar() { }, []); return ( -