@yxzpro/agent-acta 2.14.6 → 2.14.8

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/LOGFORMATS.md CHANGED
@@ -714,6 +714,12 @@ codebuddy / workbuddy 优先按上表的 `projects/` 解析(能拿到真实 pr
714
714
 
715
715
  ⚠️ 「装了 CodeBuddy CN 却一条看不到」**不是**该去 `%APPDATA%\CodeBuddy CN` 找 —— 那目录里真的没有正文;
716
716
  是 `%LOCALAPPDATA%\CodeBuddyExtension` 不存在(没装扩展)或那棵 tree 里没有 `history/` 结构。
717
+ 顶层那份 `codebuddy-sessions.vscdb` 名字最骗人(本机拆开验过):整库一张 `ItemTable`,一条会话只有一行
718
+ `session:<id>` = `{conversationId, cwd, userId, title, status, createdAt, updatedAt}` —— 只是索引,没有消息也没有 token。
719
+
720
+ 扩展根没挂上时现在有话说:诊断行、`--doctor` 与 `[discover]` 日志都会点名盘上**实际看到的目录名**,
721
+ 把四种成因分开讲(没装扩展 / 没有 `Data/` / `<acct>` 底下的 `<host>` 名不在 `vscode`·`codebuddyide`·`jetbrains`
722
+ 白名单里 / `<host>` 底下没有 `history/`),不用再远程让人敲命令问盘 —— 判据与措辞都在 `buddyExtMissingWhy`。
717
723
 
718
724
  为什么合成一行而不是并排两行:两边是同一个产品在不同宿主里的形态,拆两行会把**同一个项目的历史劈成两半**
719
725
  (按 agent 筛就漏一边,用户只会觉得「应用里的会话怎么少了一半」)。代价是一行里两类卡片的完整度不同
package/README.en.md ADDED
@@ -0,0 +1,248 @@
1
+ # AgentActa
2
+
3
+ English | [中文](./README.md)
4
+
5
+ A local request-log dashboard for AI agents: it aggregates the session logs that various AI agents write to disk, and shows per-turn/per-request latency, tokens, cache hits, context usage and tool-call detail in a browser.
6
+
7
+ - A local long-running service + SSE live push — **no external dependencies, no network egress, purely read-only** (it injects nothing and never touches any agent's config)
8
+ - One page for 24 agents: atomcode / codebuddy / workbuddy / claude / codex / cursor / trae / qoder / opencode / gemini / copilot / windsurf / codearts / kimi / dsh / zcode / doubao / hermes / devin / minimax / mimocode / kilo / openclaw / cline
9
+ - Plus a floating desktop widget (a small Electron window): today/all-time tokens + the latest card stream
10
+
11
+ Requirements: `node` on the machine (`node -v` works, `>=22.5`). **Windows is the primary verified platform**; the code paths for mac / Linux / HarmonyOS PC (per-platform candidate directories, self-healing after cross-platform moves, POSIX permissions) are in place and pinned by regression tests, but have not been run end-to-end on real machines — before switching platforms, read the "Cross-platform" section of [REFERENCE.md](REFERENCE.md), which lists where the platforms differ and the two or three things you must handle by hand.
12
+
13
+ For the full semantics, internals and troubleshooting guide see **[REFERENCE.md](REFERENCE.md)**; for the field semantics and parsing boundaries of each agent's log format see **[LOGFORMATS.md](LOGFORMATS.md)**. (Both are currently Chinese-only.)
14
+
15
+ ## Install & quick start
16
+
17
+ ### From npm
18
+
19
+ ```bash
20
+ npm i -g @yxzpro/agent-acta # installs the `agentacta` and `agentacta-mcp` commands
21
+ agentacta --open # start the service and open http://127.0.0.1:14570
22
+ ```
23
+
24
+ ⚠ The package name carries the `@yxzpro/` scope: the bare name `agent-acta` was rejected as too similar to the existing `agentacta` package (someone else's similar project). **The command name is unaffected** — you still type `agentacta`. Registry mirrors sync at different speeds; if install fails, add `--registry=https://registry.npmjs.org/`.
25
+
26
+ ### From a tarball (tgz)
27
+
28
+ ```bash
29
+ npm i -g agent-acta-<version>.tgz # installs the `agentacta` command
30
+ agentacta --install-hooks # optional: auto-start the service when a session begins
31
+ agentacta --open # start the service and open http://127.0.0.1:14570
32
+ ```
33
+
34
+ ### From a source checkout
35
+
36
+ ```bash
37
+ npm i -g <this directory> # symlink install; edits take effect immediately
38
+ node <this directory>/agent-acta-server.mjs --ensure # or skip npm entirely and start the service with one command
39
+ ```
40
+
41
+ Then open <http://127.0.0.1:14570> in a browser. Supported agents are discovered automatically — no configuration needed.
42
+
43
+ ## Command reference
44
+
45
+ | Command | What it does |
46
+ |---|---|
47
+ | `agentacta` | Run the service in the foreground (the service itself) |
48
+ | `agentacta --ensure` | Make sure the port is serving **this** version: exits idempotently if it already is, restarts if the version is older (this is what the hooks call) |
49
+ | `agentacta --open` | Start the service and open the browser |
50
+ | `agentacta --client` | Start the floating desktop widget (first run downloads ~100MB of Electron runtime into `~/.agent-acta/widget-runtime/`) |
51
+ | `agentacta --stop` | Stop the service (graceful shutdown with index flush first; kills by pid only if there's no response) |
52
+ | `agentacta --status` | Status check: is the service running, does the version on the port match the local package, is the widget open (exit code 0 = match / 1 = mismatch or unreachable / 2 = not running) |
53
+ | `agentacta --doctor` | Self-check that prints a **paste-ready report**: config parses, configured directories still exist, index shards intact and consistent, the port is running this version, page files present, current archive size (exit code 0 = no failures / 1 = failures). **Read-only** — changes neither data nor config |
54
+ | `agentacta --install-hooks [--dry] [--agent atomcode,claude]` | Write "auto-start on session launch" into atomcode / claude configs (idempotent, backs up before changing) |
55
+ | `agentacta --uninstall-hooks` | Remove the hooks written above |
56
+ | `agentacta --where` | Print its own install path + a copy-paste-ready hook command |
57
+ | `agentacta --archive` | Archive once: freeze finished log entries older than today, with detail snapshots, into `~/.agent-acta/archive/` (some agents delete their own logs — if you don't freeze them they're gone forever). A running service also archives daily on its own; this command is for manual catch-up/troubleshooting |
58
+ | `agentacta --archive --list` | List the archive without archiving |
59
+ | `agentacta --archive --dry` | Report what would be written without touching disk (can combine with `--list`) |
60
+ | `agentacta --search-reindex [--dry]` | Manually rebuild the full-text search index (what the page search box queries when you press **Enter**). Normally unneeded: the service builds the first pass 60s after startup, then runs incremental passes every 10 minutes. **Refuses to run while the service is up** (`--stop` first); `--dry` only counts |
61
+ | `agentacta-mcp` | Run as an **MCP server** (stdio): a read-only tool for clients like Claude / Cursor / Qoder — see the next section |
62
+ | `agentacta --version` / `-v` | Version + service-script fingerprint (to tell whether the port is running this build) |
63
+ | `agentacta --help` / `-h` | Usage |
64
+
65
+ Unknown arguments fail with exit code `2` instead of silently starting the service.
66
+
67
+ **After editing server code**: `agentacta --ensure` is the one step (it compares code fingerprints and restarts when they differ). Page-only edits just need a browser refresh.
68
+
69
+ ## As an MCP server (let agents query their own logs)
70
+
71
+ `agentacta-mcp` is the second command shipped in the package; it exposes this machine's logs to clients as MCP tools. It is an **independent process that reads logs and indexes directly** — it does **not** connect to port 14570 and writes nothing, so it works whether or not the long-running service is up (after the initial handshake it does a full scan of the machine's logs, ~2s, logging nothing meanwhile).
72
+
73
+ Config snippet (replace the path with your actual install — `agentacta --where` prints it):
74
+
75
+ ```json
76
+ { "mcpServers": { "agent-acta": { "command": "node", "args": ["<install dir>/mcp-server.mjs"] } } }
77
+ ```
78
+
79
+ | Tool | What it does |
80
+ |---|---|
81
+ | `overview` | Which agents exist, how many entries each, index freshness |
82
+ | `search_entries` | List turns by agent/project/time range (newest first, 50 by default) |
83
+ | `get_entry` | Full text of one entry by id (prompt, output, tool calls, LLM detail) |
84
+ | `list_sessions` | Grouped by session: how many turns, how many tokens, over what span |
85
+ | `usage_stats` | `by=day` daily / `by=model` per-model aggregates |
86
+ | `slowest_turns` | Slowest N turns + p50/p95 |
87
+ | `tool_fails` | Which tools fail / time out most (three-way ranking + per-agent coverage; timeouts count as failures, soft failures listed separately) |
88
+
89
+ Tool semantics come from the same aggregation functions as the page/HTTP API — not a separate implementation. Full docs and per-client configs are in the "As an MCP server" section of REFERENCE (Chinese-only for now).
90
+
91
+ ## As a DSH plugin (the same panel inside DeepSeek Harness)
92
+
93
+ If you have [DeepSeek Harness](https://atomgit.com) (DSH) installed, you can install it as a Cordis plugin: an "AgentActa" icon appears in the left rail, opening a full panel in the center — the same Vue3 page (served in an iframe, with filters / details / SSE live push), **no extra browser tab or long-running service needed**.
94
+
95
+ ```
96
+ dsh plugin --profile desktop add "@yxzpro/agent-acta" # npm package name
97
+ dsh plugin --profile desktop add "git+https://github.com/elegant01/agent-acta.git#v2.14.7" # or straight from the repo
98
+ ```
99
+
100
+ > The `@yxzpro/` scope exists because of npm's similarity rules: the bare `agent-acta` was rejected as too close to the existing `agentacta` (someone else's similar project, published 2026-02).
101
+ > **The product name is unchanged** — the command is still `agentacta`, and the repo, panel title and URL are all the same; only the npm identifier and DSH's bundle name follow the package name.
102
+
103
+ - `dsh plugin` forwards its argument verbatim to pnpm and accepts registry / git / tarball / path forms; for a plain file use `"file:C:/absolute/path/yxzpro-agent-acta-2.14.7.tgz"` instead (tarballs packed from a scoped package drop the `@` and turn `/` into `-`). ⚠ In Git Bash / PowerShell, **quote the whole argument** when the URL contains `#` or the package name contains `@`. Replace `v2.14.7` with the version you want (`git ls-remote --tags https://github.com/elegant01/agent-acta` lists them all).
104
+ - This project is released under the **MIT** license (see `LICENSE`); the code has no network egress and uploads no log content — it only reads files your agents already wrote to your own disk.
105
+ - **Fully quit DSH and start it again after installing** (all processes must exit — closing the window is not enough; the host has a module cache).
106
+ - If the icon doesn't show up, check these two first: ① the host gates plugin compatibility by semver (a mismatch just lands in `skippedBundles`, **no error** — the symptom is exactly "doesn't appear"); ② whether `--profile` points at the profile you actually use. Verified on **DSH 0.1.7-rc.2 / runtime 0.2.0-rc.1**.
107
+
108
+ **How it coexists with the CLI service** (the most-asked question): the plugin probes `127.0.0.1:14570` **on every request** (a ~1ms loopback probe, cached for 2s), so you don't have to choose:
109
+
110
+ | Port 14570 | What the panel gives you |
111
+ |---|---|
112
+ | Up | **Exactly that service's panel**, passed through in an iframe (data, SSE, write operations all work) — there is still only that one scanner on the machine, the plugin doesn't start another |
113
+ | Down | The plugin runs its own copy inside the host process (no port, no pid file, no idle self-stop) — the panel works as usual |
114
+
115
+ If the CLI exits mid-session, the panel switches to a small "CLI service has exited" page with a **let the plugin take over** button — it only starts when you click, never automatically: auto-starting could collide with you bringing the CLI back, creating two writers, and that decision belongs to a human.
116
+ You do **not** need to `agentacta --stop` or restart DSH just to view the panel (an earlier version made you do that — it was a bad experience and has been fixed).
117
+
118
+ **Boundaries** (deliberate, not omissions): the plugin doesn't launch the floating widget; the five routes `/` (the host's auth fallback slot), `/api/shutdown`, `/api/client`, `/api/trae/capture-key`, `/widget` are simply not registered in the host (so one misclick can't take the host process down); the panel keeps its own dark skin and doesn't follow the host's light/dark switching; the data path remains read-only-local with zero network egress. Details are in the "As a DSH plugin" section of REFERENCE.
119
+
120
+ ## Page usage cheat sheet
121
+
122
+ Open <http://127.0.0.1:14570>:
123
+
124
+ - **Filters**: agent / project / time range (today / last 7 days / last 30 days, by local calendar day) / status / keyword
125
+ - **Full-text search**: **typing** in the search box = filter the current window only (instant, free); **pressing Enter** = search the whole store — it searches the **body** of each turn
126
+ (user input + AI reply + tool args/results), across agents and across history not currently loaded, with **no time range applied by default** ("that error last week" is outside the default two days anyway).
127
+ Results take over the card list with hit snippets highlighted; the top banner honestly reports "indexed A of B entries" and "no time range applied", and whether this round was cut off by the
128
+ 200-per-query cap ("only 200 returned this time"); tick "restrict to current time range" to narrow by the selected dates.
129
+ The index is maintained incrementally in the background by the service (`~/.agent-acta/search/`); searchable about a minute after first launch
130
+ - **Cards**: click any card to expand user input, AI output, execution chain, LLM call detail and tool calls
131
+ - **Status badge top-right** = SSE connection state; click it to tune the refresh rate; the dropdown next to it enables "idle self-stop" (30 min / 2 h / 6 h, off by default)
132
+ - **Browse by session** (sidebar item 2): view full multi-turn evolution per session; dsh / zcode / trae sessions come with real timelines.
133
+ Session names come from **the product's own titles** (I14: claude-family reads `ai-title` from transcripts, falling back to `last-prompt`; dsh / gemini /
134
+ buddy / zcode / traedb / opencode / kilo / hermes / devin each have their own source); only when none exists does it fall back to the session key — **file names are never passed off as titles**
135
+ - **Sub-agent topology** (I16): sub-agent sessions indent with a `└` in the session list, and the parent's row shows "N turns total · x tok" (including itself);
136
+ the top of the right pane has clickable **jump to parent / jump to children**. The criterion is an explicit lineage key from the source (dsh's `parentSession`, claude's `subagents/` directory +
137
+ `.meta.json`, zcode/opencode's `session.parent_id`, hermes' `parent_session_id`) — **never guessed from time windows**
138
+ - **Unknown times are labeled honestly** (I16): turns whose timestamps can't be recovered (atomcode `.jsonl` files of 0 bytes / missing `turn_id`, 44.6% on this machine)
139
+ no longer impersonate the session's `updated_at` — the card says "time unknown", and they stay out of per-day statistics (the stats dialog says how many turns were left out)
140
+ - **Usage stats** (the line-chart icon in the toolbar): per-day token & latency aggregates + per-model, following the current filters;
141
+ the "latency analysis" dialog has four ranking sub-tabs — slowest turns / tool calls / cache hit rate / **tool-failure profile** (I6: which tools fail most,
142
+ with a coverage table: which sources have no per-tool failure signal at all — blank there doesn't mean "never fails")
143
+ - **Export** (download icon in the toolbar → JSON / CSV / Excel): exports exactly the currently filtered list set, and **writes the filter conditions into the file**
144
+ (first two rows of CSV, a note row in Excel, `meta.filters` in JSON) — so you can later prove "this batch was filtered by what". With a turn expanded,
145
+ the detail footer also offers **export this turn's full detail** (via `/api/entry?full=1`; tool args/results are not truncated). Pure browser download; the server writes nothing to disk;
146
+ semantics, column lists and "why Excel is SpreadsheetML" are in the "Export (R11)" section of REFERENCE.md
147
+ - **Two-turn compare** (the "compare" button on card top rows; click two cards and it opens automatically): side-by-side latency / model / LLM calls / tools / credits-or-tokens /
148
+ context usage / compaction, each metric marked with "which is bigger" and by how much; the lower half is a field-by-field view of both turns' user input, AI output and tool calls (fetched on open).
149
+ qoder only provides credits and percentages (it doesn't write tokens to disk, and printing 0 would read as "costs nothing"), so in mixed comparisons inapplicable cells show `—` and no diff is computed
150
+ - **Single-turn reproduction bundle** (the "export repro bundle" button in the card detail footer): one-click zip = that turn's **raw log segment** (cut with the parser's own turn-boundary rules,
151
+ line numbers match disk) + the full parse result + four version fingerprints + the filter conditions at export time — attach it to a group chat / bug report as-is.
152
+ The zip is hand-assembled in the browser (store mode, zero dependencies); **the server writes nothing and gains no network egress**; for the database / multi-frame-compressed sources
153
+ where raw segments can't be cut, it falls back to "original path + full parse result" and says why
154
+ - **History archive** (sidebar item 4): browse the frozen entries in `~/.agent-acta/archive/` — after a product deletes its own logs (CodeArts keeps only ~30 days), the user input / AI output / tool-call detail from back then still opens here. **Read-only side path**, not affected by the left-side filters; bottom-left also has a row of **per-agent archive switches** (turn off the ones you don't want to keep)
155
+ - **Disk usage** (sidebar item 6): per-agent breakdown of "log directory / archive / index shards" and which is biggest — see the composition before cleaning (top level of `~/.agent-acta` listed item by item, including the widget's Electron runtime). **Counts only, never deletes**; traversal runs with a budget, and if it can't finish it says "partial count" instead of showing a fake complete number
156
+ - **Parser self-test** (the sidebar item of the same name): after changing parsing semantics, see how the last regression run went — each script under `test/` with its result (green / red / timeout), duration, and **which ones were skipped this time and why**, all on one page. Data comes from `~/.agent-acta/selftest.json` (written by `node test/selftest.mjs`); this page is **read-only and runs no tests in the browser**; when the results came from a different build, a yellow bar at the top warns "this green doesn't represent the current build"
157
+ - The switch next to each agent in the sidebar = temporarily disable (config kept); `✕` = remove that agent's config (local log files untouched); `+` = add manually
158
+ - **The icon button on the cursor row = install/uninstall the token-capture hook**: cursor transcripts contain no tokens; the only local per-turn usage
159
+ comes from its own `stop` hook. One click (with confirmation) adds a command to `~/.cursor/hooks.json`, after which each turn's
160
+ input/output/cache usage is captured into `~/.agent-acta/cursor-usage.jsonl` and attached back onto the cards; click again to uninstall.
161
+ **It only adds its own entry and never touches your existing hooks**; if `hooks.json` isn't valid JSON it refuses rather than overwriting. Semantics in
162
+ the cursor section of [LOGFORMATS.md](LOGFORMATS.md), API in `POST /api/cursor-hook` in [REFERENCE.md](REFERENCE.md)
163
+ - Desktop widget (the lightweight, no-Electron way):
164
+ `msedge --app=http://127.0.0.1:14570/widget`, then pin on top with PowerToys
165
+
166
+ ## Data & configuration
167
+
168
+ - Config: `~/.agent-acta/config.json` (old locations `~/.agent-log/`, `~/.atomcode/agent-log/` are migrated automatically at startup)
169
+ - Index: `~/.agent-acta/index/<kind>.json` (incremental offsets persisted; restarts don't rescan everything)
170
+ - Archive: `~/.agent-acta/archive/<agent>/<YYYY-MM-DD>.jsonl` + `manifest.json` (entry-level freezing, kept forever by default)
171
+ - **Per-agent on/off switch**: click directly in the bottom-left of the "History archive" dialog (writes `archive.skip` in `config.json`, effective immediately, no restart);
172
+ editing the config by hand works too: `"archive": { "skip": ["cursor"] }`. **Only stops future archiving; already-frozen files are kept**
173
+ - The same section also supports `enabled` (disable automatic passes), `maxDays` / `maxMB` (caps; deletes whole-day files only). Details in the "History archive" section of REFERENCE
174
+ - Full-text index: `~/.agent-acta/search/<kind>.json` (the body of each turn, one record per source). **Maintained incrementally in the background by the service itself**
175
+ (first pass 60s after startup, then every 10 minutes); no manual intervention needed; to rebuild manually use `agentacta --search-reindex` (run `--stop` first if the service is up).
176
+ Real-machine reference numbers (evening of 2026-09-22, measured via `/api/search/status` on the long-running service): **648 sources / 5111 searchable entries** (that machine has 5431 in total;
177
+ the rest either had their source files deleted by the product or had no body in that turn to begin with) → index about **24MB**.
178
+ To lift the "index at most 8000 chars per body" cap, use the `AGENT_LOG_SEARCH_MAX_CHARS` environment variable
179
+ - Records recognize sources by **absolute path**, so after moving `~/.agent-acta` to another machine/platform, records that don't match
180
+ this machine are pruned and rescanned on load (both the log and `/api/search/status.pruned` report the numbers). On POSIX, `search/` `index/` `archive/`
181
+ are all created private (`0700`/`0600` — they contain plaintext conversations); existing files are left alone — to lock everything down at once, `chmod -R go-rwx ~/.agent-acta`
182
+ - Port: defaults to `14570`, overridable with `AGENT_LOG_PORT`
183
+ - pid file: `~/.agent-acta/server-<port>.pid`
184
+
185
+ ## Server HTTP API
186
+
187
+ | Endpoint | What it does |
188
+ |---|---|
189
+ | `GET /` | The page |
190
+ | `GET /widget` | Desktop widget page (SSE live refresh) |
191
+ | `GET /api/ping` | Liveness probe |
192
+ | `GET /api/version` | `{version, build, pid}`; build = content fingerprint of the service script |
193
+ | `GET /api/agents` · `POST /api/agents` · `DELETE /api/agents?name=` · `POST /api/agents/toggle` | CRUD for agent configs |
194
+ | `GET /api/diagnose` | Environment diagnosis: probe results and unrecognized reasons for every candidate path of every candidate agent |
195
+ | `GET /api/usage?force=1` | Disk usage: per-agent log/archive sizes + index shards + top-level composition of `~/.agent-acta` (results cached 60s; `force=1` bypasses) |
196
+ | `GET /api/selftest` | Results of the last regression self-test (passes `~/.agent-acta/selftest.json` through with three honest labels: `ageMs` / `sameBuild` / `sameParserRev`). **Runs no tests**; returns `ok:false` with a `hint` when never run |
197
+ | `GET /api/snapshot?limit&agent=&project=&range=&scan=1` | Entry snapshot (the list's main data) |
198
+ | `GET /api/daily` | Per-calendar-day token/latency aggregates (ignores limit; covers the whole filtered set) |
199
+ | `GET /api/models` | Per-model aggregates (same parameters) |
200
+ | `GET /api/analyze?range=&agent=&project=&from=&to=&top=` | Ranking analysis: `slowest` / `p50` / `p95` / `byTools` (tool calls per turn) / `byCacheRate` + `toolFails` (**I6 tool-failure profile**: `rows` gives `fail/total/rate` per agent×tool, `agents` gives coverage and "not indexed" counts). `top` caps at 50, default 10 |
201
+ | `GET /api/entry?id=&full=1` | Single-turn detail (dsh/zcode/traedb additionally return per-event `events[]`) |
202
+ | `GET /api/search?q=&re=1&agent=&project=&status=&from=&to=&limit=&offset=` | **Full-text search** (what the page search box hits on Enter). `from`/`to` are only sent when "restrict to current time range" is ticked — **omitted = no time limit**; each entry's `_snip` is the hit snippet (three plain-text segments; the page escapes them itself before highlighting). Empty `q` and invalid regexes get a 400 with the reason spelled out |
203
+ | `GET /api/search/status` | Full-text index coverage: `{entries, files, total, building, unreadable, noState, bytes, at, maxChars}` — the page banner "indexed A of B entries" uses the first four |
204
+ | `GET /api/sessions` · `GET /api/session?key=` | Session list and session detail ("browse by session") |
205
+ | `GET /api/events` | SSE: hello / update / remove / agents / settings / resync / scanning |
206
+ | `GET /api/settings` · `POST /api/settings` | Scan cadence / idle self-stop |
207
+ | `GET /api/ctxwindows` · `POST /api/ctxwindows` | Context-window table (the denominator of the claude/kimi progress bars) |
208
+ | `GET /api/archive/index` | Archive listing: `{root, days[], agents[], totals, skip[], configAgents[]}` (`skip` / `configAgents` feed the "archive switches" list) |
209
+ | `POST /api/archive/skip` | Per-agent archive switch: `{agent, skip}` → updates `config.archive.skip`, effective immediately |
210
+ | `GET /api/archive/entries?agent&day&q&limit&offset` | Archived entry list (**without** details, only a `hasDetail` flag) |
211
+ | `GET /api/archive/entry?agent&day&id` | One frozen detail snapshot (404 when gone) |
212
+ | `POST /api/shutdown` | Graceful shutdown (flush index, then exit) |
213
+
214
+ Per-endpoint field details and semantics are in the "Server HTTP API" section of [REFERENCE.md](REFERENCE.md).
215
+
216
+ ## Packaging & distribution (developers)
217
+
218
+ ```bash
219
+ mkdir -p ../dist
220
+ npm pack --pack-destination ../dist # → dist/agent-acta-<version>.tgz (bump package.json's version first)
221
+ node test/pack-smoke.mjs # smoke: unpack → run in an isolated env → probe every endpoint
222
+ ```
223
+
224
+ Regression/acceptance scripts live in `test/` (with fixtures, repeatable). Run them all and view results on the page:
225
+
226
+ ```bash
227
+ node test/selftest.mjs # default list (the ones that need nothing outside the repo), ~2 min serial
228
+ node test/selftest.mjs --only parser # only names matching (prefix or title contains the word)
229
+ node test/selftest.mjs --group parser # only one group: static / parser / cli / release / real / parity / slow
230
+ node test/selftest.mjs --all # including groups that are off by default (depends on the machine)
231
+ ```
232
+
233
+ Results are written to `~/.agent-acta/selftest.json` — that's what the sidebar "Parser self-test" page reads (details in the "Parser self-test" section of REFERENCE).
234
+
235
+ The distributed tgz **is also the DSH plugin package**: `files` includes `plugin/`, and the loading contract lives entirely in `package.json` — `exports["."]` lets the host dynamically import the server shell, `dsh.bundle.patch` points at `plugin/cordis.patch.yml`, `dsh.client.inject` lists the two host UI packages (miss them and it installs but the icon never appears). So after bumping the version, also check: `plugin/client.js` **must have no top-level `import`/`export`** (the host splices it verbatim into a plain `<script>`; ESM syntax there takes the whole DSH startup down).
236
+
237
+ ## Troubleshooting quick reference
238
+
239
+ - **No idea where to start**: `agentacta --doctor` — one pass listing the state of config / directories / indexes / port version / page files / archive; paste the whole output (**read-only**, changes nothing)
240
+ - **Page won't open / stuck on "reconnecting…"**: `curl http://127.0.0.1:14570/api/ping`; if there's no response, run `agentacta --ensure`
241
+ - **New endpoints 404 after upgrading/editing code**: the port is serving an old build — restart with `agentacta --ensure` (the criterion: the `build` fingerprint from `GET /api/version`; `--doctor` reports exactly this as `[fail]`)
242
+ - **An agent stays at 0 entries**: see the sidebar "Environment diagnosis" or `GET /api/diagnose`
243
+ - **Switching to mac / Linux (or another machine)**: just move `~/.agent-acta` over — indexes re-validate against this machine's disk and rebuild,
244
+ stale records prune themselves; **manually added** agent roots in `config.json` stay forever with 0 entries, and `--doctor` names them and hints
245
+ "these are Windows-shaped paths" — delete those entries and let auto-discovery re-detect. trae's key grabbing is Windows-only, so after switching
246
+ platforms its tokens and AI bodies are unavailable. Details in the "Cross-platform" section of [REFERENCE.md](REFERENCE.md)
247
+ - **trae shows no tokens / AI bodies**: the SQLCipher database needs a per-machine key; grab it with `tools/grab-trae-key.ps1` and fill in `traeKey` — steps in the "Trae's SQLCipher database" section of [REFERENCE.md](REFERENCE.md)
248
+ - More troubleshooting (codex user input empty, atomcode datalog, hook verification pipe stalls, source edits not taking effect, etc.) in the "FAQ" section of [REFERENCE.md](REFERENCE.md)
package/README.md CHANGED
@@ -1,5 +1,7 @@
1
1
  # AgentActa
2
2
 
3
+ [English](./README.en.md) | 中文
4
+
3
5
  本地 AI agent 请求日志面板:聚合多款 AI agent 写在磁盘上的会话日志,在浏览器里查看每一「轮次/请求」的耗时、token、缓存命中、上下文占用与工具调用明细。
4
6
 
5
7
  - 本地常驻服务 + SSE 实时推送,**无外部依赖、无网络上报、纯只读旁路**(不注入、不改各 agent 的配置)
@@ -94,13 +96,13 @@ node <本目录>/agent-acta-server.mjs --ensure # 或者不装 npm,直接
94
96
 
95
97
  ```
96
98
  dsh plugin --profile desktop add "@yxzpro/agent-acta" # npm 包名
97
- dsh plugin --profile desktop add "git+https://atomgit.com/elegant001/agent-acta.git#v2.14.4" # 或直接从仓库
99
+ dsh plugin --profile desktop add "git+https://github.com/elegant01/agent-acta.git#v2.14.7" # 或直接从仓库
98
100
  ```
99
101
 
100
102
  > 包名带 `@yxzpro/` 是因为 npm 的相似性规则:裸名 `agent-acta` 与已存在的 `agentacta`(别人的同类项目,2026-02 就发了)判为太像而被拒。
101
103
  > **产品名没改** —— 命令仍是 `agentacta`,仓库、面板标题、URL 全不变;只有 npm 标识与 DSH 的 bundle 名跟着包名走。
102
104
 
103
- - `dsh plugin` 的参数逐字转发给 pnpm,认 registry / git / tarball / path 四态;只想要文件就把上面换成 `"file:C:/绝对路径/yxzpro-agent-acta-2.14.4.tgz"`(作用域包打出来的 tgz 文件名会去掉 `@`、把 `/` 变成 `-`)。⚠ `github:` 那种缩写认的是 GitHub,本仓库在 AtomGit 要写全 `git+https`;URL 里的 `#` 与带 `@` 的包名在 Git Bash / PowerShell 里**整条加引号**。把 `v2.14.4` 换成你要的版本即可(`git ls-remote --tags https://atomgit.com/elegant001/agent-acta.git` 能看全)。
105
+ - `dsh plugin` 的参数逐字转发给 pnpm,认 registry / git / tarball / path 四态;只想要文件就把上面换成 `"file:C:/绝对路径/yxzpro-agent-acta-2.14.7.tgz"`(作用域包打出来的 tgz 文件名会去掉 `@`、把 `/` 变成 `-`)。⚠ URL 里的 `#` 与带 `@` 的包名在 Git Bash / PowerShell 里**整条加引号**。把 `v2.14.7` 换成你要的版本即可(`git ls-remote --tags https://github.com/elegant01/agent-acta` 能看全)。
104
106
  - 本项目以 **MIT** 许可发布(见 `LICENSE`),代码里没有网络出口、也不上传任何日志内容 —— 它只读你自己机器上那些 agent 已经落盘的文件。
105
107
  - **装完要彻底退出 DSH 再启动**(进程全没才算,只关窗口无效 —— 宿主有模块缓存)。
106
108
  - 图标没出现,先按这两条查:① 宿主按 semver 判插件兼容(不匹配只进 `skippedBundles`、**不报错**,表现就是不出现);② `--profile` 有没有指对你实际在用的那个 profile。已在 **DSH 0.1.7-rc.2 / runtime 0.2.0-rc.1** 上验通。
package/REFERENCE.md CHANGED
@@ -1097,8 +1097,8 @@ stdout 一行一条 JSON-RPC 消息,stderr 是诊断。回归见 `test/mcp-tes
1097
1097
 
1098
1098
  ```
1099
1099
  dsh plugin --profile desktop add "@yxzpro/agent-acta" # npm 包名(已发布)
1100
- dsh plugin --profile desktop add "git+https://atomgit.com/elegant001/agent-acta.git#v2.14.4"
1101
- dsh plugin --profile desktop add "file:C:/绝对路径/yxzpro-agent-acta-2.14.4.tgz" # 或直接给包
1100
+ dsh plugin --profile desktop add "git+https://github.com/elegant01/agent-acta.git#v2.14.7"
1101
+ dsh plugin --profile desktop add "file:C:/绝对路径/yxzpro-agent-acta-2.14.7.tgz" # 或直接给包
1102
1102
  dsh plugin --profile desktop list # 看装没装上
1103
1103
  ```
1104
1104
 
@@ -102,7 +102,7 @@ import { claudeUserText, claudeIsToolResultOnly, claudeIsCommandText } from './p
102
102
  import { buddyUserText } from './parsers/buddy.mjs';
103
103
  import { mavisUserText } from './parsers/minimax.mjs';
104
104
  import { sniffBase, candidateBases, probeBases, kimiDesktopSessionsRoots, kimiWires, kimiWireIn, isKimiWire,
105
- buddyExtDataRoots,
105
+ buddyExtDataRoots, buddyExtMissingWhy,
106
106
  dshSessionFile, hasDshSessions, zcodeDbVerify, zcodeDbFile,
107
107
  opencodeDbFile, opencodeDbVerify,
108
108
  traeDbFile, traeLogKindOf, traeLogsFromDb,
@@ -857,6 +857,10 @@ async function runDoctor() {
857
857
  if (c.kind === 'dsh' && !DSH_ZSTD_OK)
858
858
  probs.push(['fail', 'dsh 的会话是 zstd 压缩的,当前 ' + process.version + ' 的 zlib 里没有 zstdDecompressSync(需 Node 22.15+)—— 升级 Node 即可,不是路径或格式问题']);
859
859
  const root = c.sessions || c.traces || '';
860
+ // codebuddy 名下有两份落盘(CLI + genie 扩展)。扩展那半没挂上时,病历上必须说得出**为什么**
861
+ // (没装扩展 / 没有 Data/ / <host> 名不认得 / 没有 history/ 四种成因),否则只能远程敲命令问盘。
862
+ // 问的是当前盘上的实情,不是配置里那份 sessionsExtra(它可能停在上一轮扫描的结论上)。
863
+ const bxWhy = (name === 'codebuddy' && !(c.sessionsExtra || []).length) ? buddyExtMissingWhy() : '';
860
864
  if (c.enabled === false) doc('info', 'agent ' + name, '已禁用,不参与扫描');
861
865
  else if (!confAvailable(c)) doc('warn', 'agent ' + name, '根不存在:' + (root || '(空)') +
862
866
  // 「换平台/换机器」是这句话最常见的来路:整个 ~/.agent-acta 搬过来,配置里的路径还是老平台的写法。
@@ -864,9 +868,11 @@ async function runDoctor() {
864
868
  (foreignPath(root) ? ' —— 这是 ' + (root.startsWith('/') ? 'mac / Linux' : 'Windows') + ' 的路径形态,而当前平台是 '
865
869
  + process.platform + ':像是一份从别的平台搬过来的配置。删掉它让自动发现重新认(或改成这台机器上的真实路径)'
866
870
  : '') +
867
- (c.source === 'auto' ? ' —— 下次启动服务时会自己摘掉' : ' —— 手工项会一直留着(确认是卸载了就删掉它)'));
871
+ (c.source === 'auto' ? ' —— 下次启动服务时会自己摘掉' : ' —— 手工项会一直留着(确认是卸载了就删掉它)') +
872
+ (bxWhy ? ';另:genie 扩展根也未探到 —— ' + bxWhy : ''));
868
873
  else doc('ok', 'agent ' + name, c.kind + ' · ' + root +
869
- (Array.isArray(c.sessionsExtra) && c.sessionsExtra.length ? '(另有 ' + c.sessionsExtra.length + ' 个额外根)' : ''));
874
+ (Array.isArray(c.sessionsExtra) && c.sessionsExtra.length ? '(另有 ' + c.sessionsExtra.length + ' 个额外根)'
875
+ : (bxWhy ? '(genie 扩展根未探到:' + bxWhy + ')' : '')));
870
876
  for (const [lv, t] of probs) doc(lv, 'agent ' + name, t);
871
877
  }
872
878
  }
package/core/service.mjs CHANGED
@@ -84,7 +84,7 @@ import { claudeUserText, claudeIsToolResultOnly, claudeIsCommandText } from '../
84
84
  import { buddyUserText } from '../parsers/buddy.mjs';
85
85
  import { mavisUserText } from '../parsers/minimax.mjs';
86
86
  import { sniffBase, candidateBases, probeBases, kimiDesktopSessionsRoots, kimiWires, kimiWireIn, isKimiWire,
87
- buddyExtDataRoots,
87
+ buddyExtDataRoots, buddyExtMissingWhy,
88
88
  dshSessionFile, hasDshSessions, zcodeDbVerify, zcodeDbFile,
89
89
  opencodeDbFile, opencodeDbVerify,
90
90
  traeDbFile, traeLogKindOf, traeLogsFromDb,
@@ -812,6 +812,13 @@ function diagnose() {
812
812
  ? '声明式规则校验失败,已回落禁用(修好 config 里的 rules 后再手动启用):' +
813
813
  row.ruleErrors.map(e => e.rule + ' —— ' + e.msg).join(';')
814
814
  : '在页面上被手动关掉了,不参与扫描';
815
+ } else if (conf.missing === undefined) {
816
+ // conf.missing 只在扫描经过 agentScannable 时才会被赋值,所以「从没被赋过值」= 这一轮还没看过盘
817
+ // (首扫是分片的,一个 agent 一个 agent 过;或这条配置刚加进来)。这时候报「已接入 · 0 条」
818
+ // 再补一句「目录认出来了、也在扫,但一条都没解析出来」是假话 —— 它压根还没扫,
819
+ // 而 0 条只是还没轮到。首扫那几秒里整页都长这样,会把人送去查解析器。
820
+ row.state = 'pending'; row.stateText = '还没扫到';
821
+ row.note = '首扫是分片的,这一轮还没轮到它(或这条配置刚加进来)—— 现在 0 条不代表盘上没有,扫完这行会自己改口';
815
822
  } else if (row.missing) {
816
823
  row.state = 'missing'; row.stateText = '目录消失'; row.note = '配置里的目录(' + (row.sessions || row.traces) + ')现在不存在';
817
824
  } else if (!row.entries) {
@@ -881,6 +888,13 @@ function diagnose() {
881
888
  row.note = (row.note ? row.note + ' ' : '') + '⚠ 这个根同时也由「' + row.ownedBy + '」在扫:条目归属会被后扫的一方抢走(不是两份数据),两条里删掉一条即可';
882
889
  row.state = 'warn'; row.stateText = '重复的根';
883
890
  }
891
+ // codebuddy 名下有两份落盘。扩展那半没探到时,这一行看着只是「CLI 的数」,而成因有四种
892
+ // (没装扩展 / 没有 Data/ / <host> 名不认得 / 没有 history/)—— 不写出来的话,同事一句
893
+ // 「读不到 codebuddy」只能远程让人敲命令问盘。措辞与 [discover] 那行同源(buddyExtMissingWhy)。
894
+ if (name === 'codebuddy' && !(row.sessionsExtra || []).length) {
895
+ const bxWhy = buddyExtMissingWhy();
896
+ if (bxWhy) row.note = (row.note ? row.note + ' ' : '') + '(genie 扩展根未探到:' + bxWhy + ')';
897
+ }
884
898
  return row;
885
899
  };
886
900
  for (const name of KNOWN_AGENTS) {
@@ -1142,6 +1156,8 @@ function ownConfFor(name) {
1142
1156
  return cands[0] || null;
1143
1157
  }
1144
1158
 
1159
+ // codebuddy 扩展根的结论只记一份:discoverAgents 每轮扫描都会跑,日志要「变了才说一句」
1160
+ let bxWhyLogged = null;
1145
1161
  function discoverAgents() {
1146
1162
  for (const name of KNOWN_AGENTS) {
1147
1163
  const cur = agentConfs.get(name);
@@ -1263,14 +1279,20 @@ function discoverAgents() {
1263
1279
  // ⚠️ 与 trae 那种「两个源覆盖同一批会话」不同,这里两份是**互不重叠的会话**(CLI 用 uuid v7、
1264
1280
  // 扩展用 md5 串,会话 id 形状都不一样),所以不需要 purgeTraeOtherSide 那样的去重。
1265
1281
  // 每轮重算:扩展卸载了 / 数据根被挪走,死路径自己掉出去。
1266
- const bxRoots = buddyExtDataRoots().filter(r => !dirTaken({ sessions: r, traces: null }, 'codebuddy'));
1282
+ const bxAll = buddyExtDataRoots();
1283
+ const bxRoots = bxAll.filter(r => !dirTaken({ sessions: r, traces: null }, 'codebuddy'));
1267
1284
  const cb = agentConfs.get('codebuddy');
1268
1285
  if (cb && (cb.sessionsExtra || []).join('\n') !== bxRoots.join('\n')) {
1269
1286
  purgeAgent('codebuddy');
1270
1287
  cb.sessionsExtra = bxRoots;
1271
1288
  saveConfig();
1272
- log('[discover] codebuddy 扩展根:', bxRoots.join(' | ') || '(这台机器上没有)');
1273
1289
  }
1290
+ // 「扩展根没挂上」原来在日志里只有一句「这台机器上没有」,等于什么都没说 —— 成因有四种(没装扩展 /
1291
+ // 没有 Data/ / <host> 名不认得 / 没有 history/,明细见 buddyExtWhyMissing)。每轮都算一次,
1292
+ // 但只在结论变了时打一行:roots 为空是稳态,不能每轮刷。
1293
+ const bxWhy = bxRoots.length ? bxRoots.join(' | ')
1294
+ : '(没有:' + (bxAll.length ? '候选根已被别的 agent 占了' : (buddyExtMissingWhy() || '未知原因')) + ')';
1295
+ if (bxWhy !== bxWhyLogged) { bxWhyLogged = bxWhy; log('[discover] codebuddy 扩展根:', bxWhy); }
1274
1296
  }
1275
1297
 
1276
1298
  // 给「没认出来」的路径配一句**原因**。
package/package.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@yxzpro/agent-acta",
3
- "version": "2.14.6",
3
+ "version": "2.14.8",
4
4
  "author": "elegant001",
5
5
  "repository": {
6
6
  "type": "git",
7
- "url": "git+https://atomgit.com/elegant001/agent-acta.git"
7
+ "url": "git+https://github.com/elegant01/agent-acta.git"
8
8
  },
9
- "homepage": "https://atomgit.com/elegant001/agent-acta#readme",
9
+ "homepage": "https://github.com/elegant01/agent-acta#readme",
10
10
  "description": "本地 AI agent 请求日志面板:聚合 atomcode / codebuddy / workbuddy / claude / codex / codearts / gemini / cursor / kimi / dsh 等本地日志,看每轮耗时、token、缓存、上下文与工具调用",
11
11
  "bin": {
12
12
  "agentacta": "./agent-acta-server.mjs",
@@ -419,6 +419,38 @@ export function buddyExtRoot(base) {
419
419
  return null;
420
420
  }
421
421
 
422
+ // ---------------- 「扩展根为什么没探到」的病历(诊断 / --doctor 用,不参与判据)----------------
423
+ // buddyExtRoot 返回 null 有四种成因,而在页面上长得一模一样(codebuddy 那行只剩 CLI 的数):
424
+ // ① 这台机器没有 CodeBuddyExtension 目录;② 有目录但没有 Data/;
425
+ // ③ 有 Data,但 <acct> 底下的 <host> 名不在 HOSTS 白名单里(新版换宿主名就会这样,最隐蔽);
426
+ // ④ host 名对得上,但底下没有 history/。
427
+ // 不分开的话,同事一句「读不到 codebuddy」只能远程敲命令问盘 —— 这里把**实际看到的目录名**带回来。
428
+ // ⚠️ 只在用户点开诊断页 / 跑 --doctor 时调;同样只用 listDirCached,别挂到 3s 扫描路径上。
429
+ export function buddyExtWhyMissing(base) {
430
+ const dirs = d => (listDirCached(d) || []).filter(n => isDir(path.join(d, n)));
431
+ const cap = (arr, n = 6) => (arr.length > n ? arr.slice(0, n).concat('…(共 ' + arr.length + ' 个)') : arr);
432
+ if (!isDir(base)) return '目录不存在';
433
+ const data = path.join(base, 'Data');
434
+ if (!isDir(data)) return '下面没有 Data/(实际子目录:' + (cap(dirs(base)).join(' ') || '空') + ')';
435
+ const accts = dirs(data);
436
+ if (!accts.length) return 'Data/ 是空的(一个账号目录都没有)';
437
+ const badHosts = [], noHist = [];
438
+ for (const acct of accts.slice(0, 8)) {
439
+ const hosts = dirs(path.join(data, acct));
440
+ const known = hosts.filter(h => HOSTS.has(h.toLowerCase()));
441
+ if (!known.length) { badHosts.push(acct + '/ 下是「' + (hosts.join(' ') || '空') + '」'); continue; }
442
+ for (const h of known) {
443
+ const hd = path.join(data, acct, h);
444
+ if (!isDir(path.join(hd, 'history')) && !dirs(hd).some(m => isDir(path.join(hd, m, 'history')))) {
445
+ noHist.push(acct + '/' + h);
446
+ }
447
+ }
448
+ }
449
+ if (badHosts.length) return cap(badHosts).join(';') + ' —— <host> 名都不是那三个宿主(只认 ' + [...HOSTS].join(' / ') + ')';
450
+ if (noHist.length) return '<host> 名认得、但底下没有 history/:' + cap(noHist).join(';');
451
+ return 'Data/ 下有账号目录,却没探到 history/(账号目录:' + cap(accts).join(' ') + ')';
452
+ }
453
+
422
454
  // ---------------- 扫描主入口 ----------------
423
455
  // root = <Data> 目录(由 sniffBase 解析出来)
424
456
  export function scanBuddyExt(agent, root, onlyFile) {
@@ -10,7 +10,7 @@ import { HOME, isDir, isFile, listDirCached, sqliteMod } from './shared.mjs';
10
10
  import { hasMavisSession } from './minimax.mjs';
11
11
  import { OPENCLAW_STATE_DIRNAMES, openclawTranscripts } from './openclaw.mjs';
12
12
  import { clineTranscripts } from './cline.mjs';
13
- import { buddyExtRoot } from './buddyext.mjs';
13
+ import { buddyExtRoot, buddyExtWhyMissing } from './buddyext.mjs';
14
14
  // hasCursorTranscripts 里枚举转录走的是 cursor 解析器那一个函数(两种落法共用一套判定,见该函数注释)。
15
15
  // ⚠️ 这条 import 曾经漏掉过:函数体在外层 try{}catch{} 里,ReferenceError 被静默吞掉 →
16
16
  // hasCursorTranscripts **恒 false** → 自动发现整条 cursor 分支永不命中(症状 = 侧栏 cursor 恒 0 条,
@@ -1224,6 +1224,18 @@ export function buddyExtDataRoots() {
1224
1224
  return out;
1225
1225
  }
1226
1226
 
1227
+ // 「codebuddy 的扩展根为什么没挂上」——一句能直接抄进病历的话(探到了就回空串,调用方据此不写)。
1228
+ // 三个候选根(~/、%APPDATA%、%LOCALAPPDATA%)在没装扩展的机器上**全都**不存在,那种情况合并成一句:
1229
+ // 三行「目录不存在」会把真正有用的那行(目录在、结构不对)淹掉。
1230
+ // c(可选,同 basesFor 的平台上下文)是给测试用的:不传就是当前这台机器。
1231
+ export function buddyExtMissingWhy(c) {
1232
+ const bases = basesFor(BUDDY_EXT_DIRNAMES, c);
1233
+ if (bases.some(b => buddyExtRoot(b))) return ''; // 探到了就不解释
1234
+ const present = [...new Set(bases.filter(b => isDir(b)))]; // 去重:APPDATA 与 LOCALAPPDATA 被指到同一个目录时别说两遍
1235
+ if (present.length) return present.map(b => b + ' —— ' + buddyExtWhyMissing(b)).join(';');
1236
+ return '这台机器没有 CodeBuddyExtension 目录(候选:' + bases.join('、') + ')—— 没装 genie 扩展,或它的数据根被挪走了';
1237
+ }
1238
+
1227
1239
  // re-export 让 sniffBase 能直接用 hasMavisSession(定义在 parsers/minimax.mjs):
1228
1240
  // 不在头部 import 是为避开「discovery 静态 import minimax.mjs → 服务启动必须先解析 minimax.mjs」
1229
1241
  // 的耦合;minimax.mjs 现在只 import shared.mjs,本身已是轻量模块,import 进来即可。