Skip to content

sync: upstream main (15 commits) + fix(server): wire custom_model_pricing at startup - #2

Merged
godlockin merged 33 commits into
mainfrom
feat/apple-silicon-local
Jul 3, 2026
Merged

sync: upstream main (15 commits) + fix(server): wire custom_model_pricing at startup#2
godlockin merged 33 commits into
mainfrom
feat/apple-silicon-local

Conversation

@godlockin

Copy link
Copy Markdown
Owner

Summary

Brings fork/main up to date with the latest
kenn-io/agentsview@`d6d3272` and fixes a wiring gap
discovered while validating the new code path.

Upstream sync (15 commits)

Merge was clean — the upstream changes moved no files
we had touched in the fork, so no manual conflict
resolution was needed.

fix(server): wire custom_model_pricing at startup

`cmd/agentsview/seedPricing` writes the LiteLLM
fallback snapshot to `model_pricing` and fires a
background multi-source refresh, but never applied
`cfg.CustomModelPricing` to the running DB. The CLI
statusline and pg serve paths call `applyCustomPricing`
explicitly, so the bug only showed up for the embedded
HTTP server. Without this fix, fork-private models
priced via `[custom_model_pricing]` in config.toml
were silently priced at $0 in the dashboard.

The read path was already correct (`loadPricingMap`
merges `db.customPricing` on top of whatever
`model_pricing` returns), so the fix is just plumbing
`applyCustomPricing(database, cfg)` after `seedPricing`
in `cmd/agentsview/main.go`.

Validation

  • `go vet ./cmd/agentsview/...` clean.
  • `go test -short ./cmd/agentsview/...` clean (24s).
  • Smoke test on the live `~/.agentsview/sessions.db`
    with the four fork models in
    `[custom_model_pricing]` returns expected daily
    cost.
  • `make build` and the Apple-Silicon arm64 cross
    build succeed.

wesm and others added 30 commits June 30, 2026 11:24
A malformed desktop updater manifest can strand users when their local archive has already been upgraded by a newer CLI. In that state the desktop app depends on Tauri's updater path for recovery, but Tauri must parse the whole `latest.json` before it can offer the macOS or Windows update.

This hardens the release path that produced the bad manifest. The AppImage repair step now normalizes Tauri signer output back to the bare base64 signature payload before it can be uploaded, and the desktop release health check rejects invalid JSON or non-base64 platform signatures instead of only checking the version field.

The immediate `updater` release assets for 0.35.0 were repaired separately so existing 0.34.x desktop users can discover the update without waiting for this change to ship.

Co-authored-by: Wes McKinney <wesm@users.noreply.github.com>
…ts (kenn-io#926)

Documentation update for the 0.35.0 release. Builds on the release-notes
commit with accuracy corrections found by checking each changelog entry and
the changed docs against the code, plus documentation and screenshot
scaffolding for new 0.35.0 features.

## Changelog corrections

Each 0.35.0 entry was checked against its implementing commit and code:

- **S3 scope** (see note below): narrowed "many file-backed agent session
  roots" back to "Claude and Codex session roots".
- Dropped "timeout visibility" from the two remote-sync entries; kenn-io#854 and kenn-io#911
  surface per-phase elapsed time, not timeouts.
- Native zoom applies to the whole desktop app, not Windows only (kenn-io#850).
- Reworded the Gist token fallback (kenn-io#865 adds a fallback; nothing was broken)
  and the resync regression fix (kenn-io#912 fixes the regression itself, not only its
  reporting).
- Added the Gemini sandboxed-insight fix (kenn-io#852), which is user-facing but was
  unlisted.
- Singularized the installer entry (a single binary).

## New-feature documentation (usage.md)

Features that shipped in 0.35.0 without user docs: the Skim message layout
(this corrects "three layouts" to four), high-contrast mode and the text-size
control, sidebar batch selection, the Language setting, and the corrected
Settings section list.

## Screenshots

Adds Playwright tests and asset-manifest entries for two new screenshots, the
dashboard model filter and the Recent Edits page, referenced from usage.md and
recent-edits.md. The manifest lives in three synced places (the two asset
scripts and `scripts/docs_assets_test.go`); all three are updated.

The `docs-generated-assets` orphan branch must be regenerated and pushed for
these two images to resolve. Until then the docs build expects images that do
not exist yet, so the orphan push needs to land with this change.

## S3 wording, flagged for discussion

The branch had generalized the S3 docs from "Claude and Codex" to "many
file-backed agent session roots." On this branch only Claude
(`internal/parser/discovery.go:423`) and Codex
(`internal/parser/codex_provider.go:217`) discover S3 objects; the generic
`JSONLSourceSet` does not enumerate S3, and processing errors on any other
agent (`internal/sync/engine.go:3282`, `unsupported s3 agent type`). The
source-set framework is written to generalize, so this is extensible, but no
third agent is wired today. Reverted to Claude/Codex for accuracy. Happy to
restore the broader wording if a follow-up wires more agents.

## Where to look

- `docs/changelog.md` for the corrections.
- `docs/usage.md` for the new-feature sections.
- `docs/screenshots/tests/screenshots.spec.ts` and the three manifests.


Co-authored-by: Wes McKinney <wesm@users.noreply.github.com>
* feat: add standalone quack sync path

Quack now has its own config, environment variables, CLI command group, daemon push route, watch path, and read selector instead of being inferred from PostgreSQL targets. PG sync keeps the PostgreSQL-only URL/schema/machine behavior, while Quack maps to the existing DuckDB backend only at the Quack boundary and derives the internal machine label from the host.

This keeps PG and DuckDB behavior separate while making Quack usable where a read-only mirror or push sync backend is expected.

* fix: keep duckdb push local

The standalone Quack path made the shared DuckDB push helper URL-aware so Quack could push to a remote endpoint. That also changed regular duckdb push when users had [duckdb].url configured for remote reads, because the helper preferred URL over Path and could try to attach Quack instead of updating the local mirror file.

Keep DuckDBPush on the explicit local file opener and reserve URL-aware opening for the Quack push path. A regression test covers configs that include both a local DuckDB path and a remote read URL.

* fix: keep daemon duckdb and quack push scoped

The HTTP push daemon still used URL-aware DuckDB construction for duckdb push, so a posted DuckDB config with a Quack read URL could make the daemon target the remote endpoint while the local CLI backend correctly wrote the local mirror file. The daemon route now opens the posted DuckDB path explicitly, preserving existing duckdb push behavior across direct and daemon modes.

Quack push also now owns its sync-state scope at the handler boundary instead of accepting an empty or caller-provided request value. This keeps Quack watermarks separate from the local DuckDB mirror state while preserving project filter handling.

* fix: keep quack serve tokens out of output

Quack exposes auth_token in the CALL quack_serve result, and SQL-printing launch paths can dump that table into logs. agentsview now starts Quack with the table-function form projected to listen_uri and listen_url, keeping the token as an input argument only instead of requesting it back in the result set.

The DuckDB Quack startup summary also stops printing generated token values and reports only whether the token was generated or configured. The Quack smoke path uses the same non-secret projection, and an fd-level integration test covers process stdout and stderr during startup.

* fix: reuse duckdb path for quack sync

Quack sync should not introduce a parallel config, command, or daemon API when the existing DuckDB backend already owns local-file and remote-URL targets. This collapses the branch-added Quack config and push/read surfaces into DuckDBConfig, so AGENTSVIEW_DUCKDB_URL and AGENTSVIEW_DUCKDB_TOKEN drive remote Quack pushes through the normal duckdb push route.

Continuous sync is preserved as duckdb push --watch rather than a top-level quack command, and DuckDB status now opens URL-backed stores through the same config path. Startup output for serve modes now reports that auth is configured without printing bearer token values, because those streams can land in logs.

* fix: keep duckdb status machine scoped

DuckDB status was moved onto the config-backed store opener so URL-backed Quack endpoints could be queried without a local sync handle. That path accidentally counted every mirrored machine, while the existing DuckDB status output had always reported this machine's session and message counts. Shared mirror files and remote Quack endpoints could therefore show global totals in a per-machine status view.

Keep the config-backed status reader on the same machine-scoped queries as Sync.Status, while still allowing the URL-backed store opener to handle remote Quack endpoints. The regression test seeds another machine into the mirror and verifies the status reader does not include it.

* fix: require explicit quack serve token

Starting a Quack server with an auto-generated token is not useful once startup output stops printing secrets, because clients have no safe way to discover that token. Persisting the generated value would also create another durable secret sink just to recover from a logging concern.

Require the existing DuckDB token path instead: --token, AGENTSVIEW_DUCKDB_TOKEN, or [duckdb].token. Startup output continues to report only that a token is configured, so systemd or terminal logs do not receive bearer-token material.

* fix: scope duckdb remote push state

Remote Quack pushes reuse the DuckDB sync watermark, so different URL-backed targets could share duckdb_last_push_at. Switching targets after pushing another remote could skip unchanged sessions missing from the new target.

Derive a non-secret sync-state scope for URL-backed DuckDB targets and pass it through local, daemon, and server push paths. Local file mirrors keep the historical unscoped key for compatibility, and duckdb status reads the same scoped watermark.

Keep the adjacent token handling explicit: Quack serve help no longer promises generated tokens, and Quack URL redaction now strips URL userinfo plus secret-like query values before errors can print them.

* fix: handle sigterm in duckdb push watch

Service managers commonly stop long-running watch commands with SIGTERM rather than an interactive interrupt. DuckDB push watch used the narrower interrupt-only context, so it could exit before the watch loop had a chance to perform its cancellation path and final flush, unlike the PostgreSQL watch flow.

* fix: redact native quack secrets

Native Quack URLs do not parse like HTTP URLs, so userinfo and fragments could survive the existing redaction path even though attach errors include the redacted URL. Strip native userinfo, secret query values, and fragments before logging those endpoints.

The auth recovery prompt also needs to reference token sources available before browser authentication succeeds. Point it at the persisted config token or AGENTSVIEW_AUTH_TOKEN instead of the server console or settings page, which may be unavailable in auth-required mode.

* fix: address duckdb roborev findings

Roborev flagged several low-risk follow-ups across the DuckDB/Quack path: duplicated status-count SQL, stale docs for explicit Quack serve tokens, incomplete remote-scope coverage, and an inconsistent signal helper.

Centralize DuckDB status counting through the config-backed helper, keep non-secret selector query parameters in remote target fingerprints, document that Quack serve tokens must come from the existing DuckDB token sources, and reuse one DuckDB long-running signal set for push and serve commands.

* fix: honor auth token env override

The auth recovery flow now points users at AGENTSVIEW_AUTH_TOKEN, so the server config loader needs to treat that variable as a real token source instead of allowing config.toml to silently override it. This keeps supervised deployments and pre-auth browser recovery consistent with the documented token lookup path.

Refresh the Zensical docs for the same PR behavior: DuckDB push watch, URL-backed DuckDB push/status/serve, explicit Quack serve tokens, and auth tokens no longer being printed to startup output.

---------

Co-authored-by: Wes McKinney <wesmckinn+git@gmail.com>
…n-io#349) (kenn-io#934)

GitHub Copilot bills via a monthly premium-request quota rather than per-token, so its session files frequently record no per-message token usage. `usage daily --agent copilot` then shows all-zeros / $0.00 with no explanation, which reads as "spent nothing" (kenn-io#349).

This adds a one-line stderr note from `usage daily` when the agent filter is an all-Copilot filter and the totals are zero. The agent filter accepts comma-separated lists, so `IsCopilotAgentFilter` treats an all-Copilot list (e.g. `copilot,vscode-copilot`) as Copilot. The note is an agent-property statement shown in response to an explicit `--agent` the user typed, so it needs no session-presence check.

Scope: this is the CLI half of kenn-io#349. The dashboard hint is the harder half — to avoid showing it for an empty view it needs a timezone-correct, filter-faithful matching-session count from the sessions table (to distinguish "Copilot sessions with no usage" from "no matching sessions"), which is a cross-backend change worth its own PR. Tracked separately; this PR is intentionally scoped to the CLI.

## Where to look

- `cmd/agentsview/usage.go` (`noTokenDataNote`).
- `internal/db/usage.go` (`NoTokenData`, `IsCopilotAgentFilter`).


Co-authored-by: Matthew Jacobs <mjacobs@users.noreply.github.com>
Desktop startup now renders reportable failure details in the Tauri loading window when the bundled backend fails to launch, errors, or exits before readiness. It also keeps waiting through long daemon migrations and full resyncs instead of treating a five-minute startup as failed, while still reporting clearly absent or incompatible daemons.

Co-authored-by: Wes McKinney <wesm@users.noreply.github.com>
OpenClaude sessions are invisible today because the parser registry has no OpenClaude family, no default `~/.openclaude/projects/` source, and no discovery or parser entry point that can emit OpenClaude rows into agentsview's normal session pipeline.

This adds OpenClaude as its own parser family with distinct `openclaude:` IDs, parser-owned discovery, source lookup, and config wiring for both `OPENCLAUDE_PROJECTS_DIR` and `OPENCLAUDE_CONFIG_DIR`. The parser follows the real OpenClaude on-disk layout, carries over compatible Claude JSONL handling, and adds the OpenClaude-specific pieces the stored rows need, including title events, aggregate token totals, queued prompt filtering, top-level meta-user suppression to match Claude parity, subagent lineage, and termination status classification.

The scope stays inside parser registration, parser discovery, parser parsing, and the matching supported-path docs. It does not widen into sync routing, server, storage, frontend, or broader model-management work.

Closes kenn-io#514


Co-authored-by: Rod Boev <rodboev@users.noreply.github.com>
Read commands still had compatibility paths that could open SQLite directly when daemon discovery did not yield HTTP. That undermined the daemon-first migration and left upgraded users exposed to old schema/read-only failures instead of letting the writable daemon own migrations.

This makes daemon-capable reads resolve through daemon transport: they use reachable daemons, start or replace the local daemon where appropriate, and fail with restart guidance when the daemon is unreachable or incompatible instead of falling back. The remaining direct DB access is limited to commands whose contract is explicitly local, offline, or writer-owned, such as raw source export and offline archive queries.

<sup>generated by a clanker</sup>

Co-authored-by: Marius van Niekerk <mariusvniekerk@users.noreply.github.com>
Document the 0.35.1 release after the tag was cut so the public changelog, supported-agent entry points, Copilot usage guidance, and DuckDB/Quack command notes match the shipped behavior.
…n-alias backfill marker (kenn-io#940)

## Problem

On a shared PG hub, `pg push` repeatedly re-runs the one-time session-alias backfill (kenn-io#898) as a full ~30-min sweep and never records it as done, so `pg status` shows `Last push: never` and every push is a cold full sweep.

`completeSessionAliasBackfill` only writes `pg_session_alias_backfill_v1` when `Errors == 0 && SkippedConflicts == 0`. But `SkippedConflicts` counts sessions owned by **another** machine (`errSessionOwnershipConflict`) — a permanent, expected condition on any shared hub. A host that doesn't own 100% of the matching sessions can therefore never complete the backfill, and `applySessionAliasBackfillRequirement` forces a full push on every run.

## Change

Gate the marker on real push `Errors` only. Skipped ownership conflicts are other machines' sessions this host neither can nor should re-push, so they don't indicate an incomplete backfill of *this host's own* sessions. The `Errors > 0` guard is kept, so a push that actually failed to write this host's sessions still re-forces full next time.

`TestCompleteSessionAliasBackfill*` now covers: clean → marked; errors → deferred; skipped-conflicts → **marked**; errors+skipped → deferred.

## Scope

This fixes one of the two causes in kenn-io#939. The other — the backfill marker also being **per-project-filter scoped**, so any change to the filter set re-triggers a full sweep — is left for discussion in kenn-io#939 because keying it by PG target instead touches transition-window semantics that are a maintainer call.

Refs kenn-io#939. Follow-up to kenn-io#891.


Co-authored-by: leejuhanKr <leejuhanKr@users.noreply.github.com>
Document the 0.35.2 bugfix release after the tag was cut so the public changelog matches the shipped release notes. The entry calls out the documentation refresh and PostgreSQL push-sync fix, and it credits Wes McKinney and leejuhanKr for the shipped work.

This keeps the published release history aligned with the 0.35.2 tag without changing product behavior.

Co-authored-by: Wes McKinney <wesm@users.noreply.github.com>
…#937)

This PR adds Traditional Chinese (zh-TW) localization to the frontend messages by translating the Simplified Chinese (zh-CN) values and registering the locale in Inlang settings.

Co-authored-by: Linus <Playgrand-by-linus@users.noreply.github.com>
Windows CI has been spending a growing amount of time in unit-test setup rather than in the assertions themselves. This branch consolidates compatible read-only or sequential fixture scenarios so repeated SQLite archive, sync engine, HTTP server, and provider setup is reused where the tests are already isolated by IDs, projects, dates, or source paths.

The behavior checks remain in place as subtests, while mutation-heavy and failure-path cases stay separate. The main tradeoff is that a few test functions are larger, but they now make the shared fixture boundaries explicit and avoid paying the same setup cost over and over on Windows.

The branch also adds repo-level LF normalization so Git for Windows no longer falls back to contributor-local autocrlf behavior and produces line-ending-only churn during this kind of test work.

Reviewers should look closely at the fixture consolidations in internal/sync, internal/db, cmd/agentsview, and internal/server to confirm each merged case still preserves the original preconditions and does not mask unintended state coupling.

Co-authored-by: Wes McKinney <wesm@users.noreply.github.com>
AgentsView can resume or fork a whole Claude session, but it cannot start from the message where the user actually wants to branch. The session detail view already exposes message ordinals, and the backend already owns resume command construction, so the missing piece is a message-point contract between the message UI and the resume handler.

This adds a Claude-only message-point fork path that accepts a session ID plus message ordinal, validates that the ordinal belongs to the local Claude session, and builds a deterministic context prefix for a new Claude run. The existing whole-session resume and fork behavior stays unchanged when no ordinal is supplied, and storage remains a data source rather than the place where fork policy lives. Embedded child-session transcripts keep their own session identity, local read-only mode falls back to a copyable command, remote read-only mode hides the action, and each generated prompt file is isolated per launch and self-cleaning when run.

The frontend adds the affordance at the message level instead of overloading the breadcrumb session menu. The focused coverage should prove whole-session preservation, unsupported-agent handling, missing-ordinal handling, and the message-level request body.

Fixes kenn-io#107


Co-authored-by: Rod Boev <rodboev@users.noreply.github.com>
Visual Studio Copilot sessions from Visual Studio 2026 are currently missed because the parser only discovers the older `VSGitHubCopilotLogs/traces` layout. The new Visual Studio layout stores Copilot Chat data under each project’s `.vs/<ProjectName>/copilot-chat/<thread>/sessions/<GUID>` tree, so a configured project root or `.vs` root produces no Visual Studio Copilot sources today.

This teaches the existing Visual Studio Copilot provider to recognize the project-local session layout while keeping the current shared-trace behavior intact. Discovery, changed-path classification, watch planning, and source identity stay in the provider that already owns the Visual Studio Copilot surface, and payload parsing still reuses the current normalization path where the on-disk JSONL shape matches. The update also keeps mixed legacy and VS 2026 storage on one canonical session source, so the same conversation ID does not get rewritten from two competing file identities.

The focused coverage proves that project-root and `.vs`-root discovery find Visual Studio 2026 session files, that delete and write handling classify the new layout correctly, that the watch plan covers the nested session roots Visual Studio 2026 writes under, that non-GUID extensionless files stay rejected, and that legacy `*_VSGitHubCopilot_traces.jsonl` sources still discover and resolve exactly as before.

Fixes kenn-io#890


Co-authored-by: Rod Boev <rodboev@users.noreply.github.com>
kenn-io#950)

Fixes kenn-io#944.

Callers building against `agentsview mcp` had to read the Go source to learn why `message_count` never matches any `get_messages` total, or whether `sum(filtered) + sum(returned) == message_count` is safe to rely on: the output schemas exposed `message_count`, `user_message_count`, `filtered`, and `next_from` with no descriptions.

## What changed

The semantics are now documented where MCP clients actually see them -- the generated output schemas -- using the same `jsonschema` tag mechanism that already documents the input parameters:

- `message_count` counts every stored message across all roles, including system messages. No `roles` filter can make a `get_messages` total match it, because `get_messages` unconditionally drops system rows.
- `filtered` is the count of scanned messages a page excluded via the role/system filter; across a full pagination sweep, returned plus filtered adds up to `message_count`.
- `user_message_count` and `next_from` gained descriptions in passing: they sit on the same structs, and `next_from`'s "page until absent, not until a short page" behavior is easy to misuse from outside.

The `get_messages` tool description states the reconciliation as well, and a new regression test sweeps a mixed-role session (user/assistant turns, an `is_system` row, tool dumps, and a legacy system-prefixed user row) to exhaustion under three `roles` filters, asserting `returned + filtered == message_count`. That takes the issue's open question -- documented invariant or coincidence of shared filtering logic -- and resolves it as a deliberate, pinned contract.

## Limitations

The reconciliation is documented unqualified, but it strictly holds against the stored message rows, which equal `message_count` for a session at rest. A session mid-sync can drift briefly (the session row and its message rows are written in separate steps), and a sweep racing new appends can land on either side. If you'd rather the schema wording carry an "at rest" qualifier, happy to adjust.

Reviewers should look at the field tags in `internal/mcp/tools.go`, the tool description in `internal/mcp/server.go`, and `TestGetMessages_FilteredReconcilesWithMessageCount` in `internal/mcp/tools_test.go`.


Co-authored-by: Matthew Jacobs <mjacobs@users.noreply.github.com>
Anyone who wants to pin a deployment to "the latest released version" instead of tracking `main` commit-by-commit currently has nowhere to point. The Docker publishing workflow only produces `latest`, which moves on every push to `main` and can include unreleased work between tags, and exact semver tags like `v1.4.2`, which never move and require manually bumping the pin on every release. There is no tag whose meaning is "most recent tagged release," so `docker-compose.prod.yaml` and other consumers wanting release-only stability are stuck choosing between a moving branch build and hand-maintained version pins.

This change adds a single additive `stable` tag to `.github/workflows/docker.yml`'s existing `docker/metadata-action` configuration, enabled only when the workflow runs from a version tag push, the same trigger that already produces the semver tag. `latest` keeps its current behavior and continues to track `main` on every push; the new `stable` tag simply moves to point at whichever tagged release was published most recently. Nothing else in the workflow, the Dockerfile, or the build/push steps changes. `docker-compose.prod.yaml` is intentionally left on `latest`, so the documented default deployment path is unaffected; `stable` is an additive option for consumers who want to track releases rather than `main`.

The workflow YAML was validated for syntax locally, but actually publishing a `stable` tag to `ghcr.io/kenn-io/agentsview` on a real tag push can only be verified by the Docker workflow itself, so CI on this PR and on the next tagged release is the authoritative proof that the tag publishes as intended.

Fixes kenn-io#923


Co-authored-by: Rod Boev <rodboev@users.noreply.github.com>
Adds the shared branch-filter foundation for a scoped `(project, branch)` dimension.

This layer adds branch-token parsing, branch predicates, branch metadata listing with an encoded token clients can pass back, matching SQLite/Postgres/DuckDB behavior, and generated client support for the shared filter shape. Follow-up branches can build CLI, MCP, activity, usage, and UI affordances on one API contract.

Tracking issue: kenn-io#928

Follow-ups are staged as fork-internal PRs that fan out from this branch:

- Filter surfaces: prateek#1
- Activity rollups: prateek#2
- Usage grouping: prateek#3

Those PRs stay in the fork until this foundation lands. After that, each one can be restacked onto `main` and opened upstream independently.

Co-authored-by: Prateek Rungta <prateek@users.noreply.github.com>
The serve daemon paid O(session history) on nearly every watcher-triggered
write: ~4,700 session updates per day on an active machine, each rescanning or
rewriting a whole session to absorb a few appended lines. This PR removes the
hot paths; the daemon's steady-state work now scales with new data instead of
archive depth.

**Debounced signal recompute (internal/sync/signal_schedule.go).** The
incremental append path ran recomputeSignalsFromDB synchronously per write: a
full reload of the session's stored messages, the definite-rule secret regex
scan over all of them, and a delete+reinsert of the session's secret findings.
writeIncremental now marks the session on a per-session scheduler instead. The
first write after a quiet period recomputes inline, writes during a streaming
burst coalesce into at most one recompute per 10s, and a one-shot timer
flushes 2s after writes go quiet. No timer is armed while nothing is dirty.
Engine.Close cancels the pending timer, waits for any in-flight timer
recompute, and flushes the rest, so no recompute is still using the DB when
it returns; every engine owner (serve, sync, session sync, usage, remote
import, pg watch, and the server's on-demand engine via Server.Shutdown)
closes the engine before the DB. Push paths flush pending recomputes before
scanning rows — pg watch via Engine.FlushSignals in its sync step, and
SyncThenRun (server PG/DuckDB pushes) via an inline flush under syncMu — so
PostgreSQL never receives stale signal fields. Timer- and
flush-driven recomputes take the engine's sync lock (inline runs already hold
it via writeIncremental), so a delayed recompute can never read an older
message snapshot and then overwrite signals written by a concurrent sync.
Tradeoff:
health/secret signals for an actively-streaming session can lag by up to ~12s;
message content itself still indexes immediately.

**Diff-based message replace (internal/db/messages_diff.go).** Claude
streaming sessions frequently hit the chunk-merge/agent-linkage full-parse
fallbacks, and each one ran ReplaceSessionMessages/ReplaceSessionContent as a
full delete+reinsert — every message row and FTS entry rewritten to merge one
tail chunk, which also kept the WAL hundreds of MB large. Both entry points
now diff incoming messages against stored rows by ordinal and write only
changes: unchanged rows are untouched (keeping rowids, pins, and FTS entries),
the merged tail is updated in place, and new rows are appended. Equality is
defined on the exact persisted tuples by sharing the insert argument builder
and resolve helpers between the insert and diff paths. The diff falls back to
the existing full replace when it cannot preserve row identity: truncations,
duplicate or vanished ordinals, changed rows whose source_uuid differs (pins
are re-matched by uuid on the full path), or rewrites touching more than half
the stored rows. The bulk initial-sync write path is unchanged.

**Root-gated changed-path classification (internal/sync/classify_changed_path.go).**
A live CPU profile of the patched daemon showed the next hot spot:
classifyProviderChangedPath ran ListStoredSourcePathHints for every
provider-authoritative agent's watch roots on every watcher event, and the
LIKE ... ESCAPE hint query cannot use an index, so each event cost ~30 agents'
worth of sessions-table scans. Every SourcesForChangedPath implementation
resolves the changed path within the provider's configured roots or plan watch
roots, and the hint query already scopes stored paths to the watch root, so an
agent whose roots cannot contain the path never claims it. Classification now
skips such agents before their per-root hint queries, cutting the per-event DB
work to the one or two agents whose roots overlap the changed path.

**Hidden serve --pprof flag.** The deployed binary is stripped, so a hot
daemon could not be profiled. serve --pprof registers net/http/pprof under
/debug/pprof; off by default and hidden, mirroring the sync command's
profiling flags. When mounted, the pprof routes are gated by the same bearer
auth and Host-header (DNS-rebinding) checks as /api/ routes; with pprof
disabled the path remains ordinary SPA fallback.

Reviewers should start with planSessionMessageDiff's fallback conditions
(internal/db/messages_diff.go) — they encode every case where in-place updates
would change observable behavior — the scheduler's leading-edge/trailing-
flush semantics in internal/sync/signal_schedule.go, and the root-containment
invariant asserted in the classifyProviderChangedPath gate comment.

🤖 Generated with [Claude Code](https://claude.com/claude-code)


Co-authored-by: Wes McKinney <wesm@users.noreply.github.com>
Worktree project mappings currently skip any historical session row whose stored `cwd` is empty, even when another row for the same transcript file proves the real working directory. That leaves older sessions stuck on stale project names after a user adds or reapplies a worktree mapping, which is especially visible after a worktree disappears and the user tries to recover those sessions under the correct project.

The path matcher itself is not the bug. It is correct to reject an empty `cwd`. The gap is that both scan-based apply paths discard empty-`cwd` rows before matching, even though those rows still carry `file_path` and can have a same-file sibling with a real `cwd`. This change keeps the fix in `internal/db`, adds a match-only fallback `cwd` for scanned rows, and uses a same-`file_path`, non-deleted sibling row only to decide which mapping applies. The row's own stored `cwd` stays untouched, and the existing optimistic-concurrency update guard keeps comparing against that stored value.

Scope stays narrow on purpose. The live single-session sync path already receives a caller-supplied `cwd`, so this PR leaves it alone and fixes only the historical backfill surfaces that scan stored rows. Rows with no usable sibling remain unmatched exactly as they do today, and rows that already have non-empty `cwd` keep their current behavior.

Fixes kenn-io#942


Co-authored-by: Rod Boev <rodboev@users.noreply.github.com>
tkmx-client still needs Cursor attribution parity before it can retire the last legacy Cursor collector. AgentsView already ingests Cursor transcripts, but it still ignores `~/.cursor/ai-tracking/ai-code-tracking.db`, so the stats payload has no commit-level tab, composer, human, or conversation-count attribution from Cursor itself.

This keeps the scope to the surviving Cursor attribution slice from kenn-io#358. The new reader lives beside the existing Cursor parser, can be redirected with `AGENTSVIEW_CURSOR_ATTRIBUTION_DB`, follows Cursor's live schema by windowing scored commits on `scoredAt` while leaving the text `commitDate` column as metadata, and leaves transcript discovery untouched. Aggregation stays owned by service stats code, and the result lands in a dedicated top-level `cursor_attribution` block rather than being mixed into git-derived `outcome_stats` or folded into the session-derived `agent_portfolio` maps. That preserves the meaning of the existing stats owners while giving downstream consumers one explicit Cursor attribution surface to read.

Project include and exclude filters still suppress the block because Cursor's local attribution DB has no project key, and non-Cursor agent filters keep it nil for the same reason. The human-readable stats output only renders the section when the service-owned block is present.

PR kenn-io#413 stays separate. It covers Cursor `state.vscdb` session ingestion, which is a different Cursor data source from `ai-code-tracking.db` and does not provide the line-attribution totals required here.

Closes kenn-io#358


Co-authored-by: Rod Boev <rodboev@users.noreply.github.com>
Closes kenn-io#955.

OpenCode-family SQLite readers now use a real read-only file URI and no longer request a journal-mode change while inspecting source databases. Changed-path classification ignores WAL-index (`-shm`) events and empty, header-only, removed, or otherwise non-data WAL lifecycle events, while retaining main-database and data-bearing WAL updates.

This prevents AgentsView's own read connections from recursively fanning one sidecar event out to every SQLite-backed OpenCode session. Checkpointed WAL removal relies on the corresponding main-database write, and live uncheckpointed WAL frames remain visible to the parser.

The provider watch plan now declares only the main database and WAL data file. Regression coverage uses a real writer-held WAL to verify that committed frames are classified and readable, while transient sidecars remain bounded no-ops.


Co-authored-by: Trent Nelson <tpn@users.noreply.github.com>
… suite (kenn-io#951)

The shared test-DB templates from kenn-io#943 checkpoint their WAL under a 1-second context deadline inside a `sync.Once` that caches the error. On a cold windows-latest runner, one checkpoint that misses the deadline permanently poisons the template, and every subsequent test in that binary fails with the same cached `checkpointing db template: wal checkpoint truncate: context deadline exceeded`. That is what happened on kenn-io#948's CI run — a frontend-only diff — where the `cmd/agentsview` and `internal/db` binaries each hit the deadline once and 599 tests failed in cascade, while the identical SHA passed on main's push run.

Two changes, by blast radius:

- The two base templates (`internal/dbtest/dbtest.go`, `internal/db/db_test.go`) get a best-effort checkpoint with a generous once-per-binary deadline: the copy step already carries the `-wal`/`-shm` files along, so an uncheckpointed template is still a consistent database, and losing the checkpoint only costs compactness. Independently, a failed template build now degrades to creating the per-test database from scratch (the pre-kenn-io#943 path) instead of failing the test — the template is a setup-cost optimization and should never be a single point of failure for the whole binary.
- The five seeded fixture builders (store contract, chunked analytics, daily usage, server analytics, cmd stats golden) keep their fatal checkpoint semantics — their consumers' copy semantics vary — but move from the 1s deadline to the same 30s one, which is effectively free since each runs once.

Two regression tests cover the new fallback, including cleanup of a partial template copy left behind by a mid-copy failure and the existing-file short-circuit.

Not addressed here: the unrelated `TestEnsureBackgroundServeLaunchLoser…` "server did not become ready within 2s" readiness flake that hit kenn-io#949's run.

Where to look: `buildTestDBTemplate` in `internal/dbtest/dbtest.go` and `copyTestDBTemplate`/`openTestDBWithTemplate` in `internal/db/db_test.go`.


Co-authored-by: Matthew Jacobs <mjacobs@users.noreply.github.com>
GitHub Copilot sessions really do arrive without per-message token counters or per-message cost, so the Usage dashboard's current zero-state is misleading rather than accurate when a user filters down to Copilot-only agents. The page shows zero-value cards and generic empty panels even when matching Copilot sessions exist in the selected range, which reads as "nothing was spent" instead of "this agent family does not expose token usage."

PR kenn-io#934 already fixed the CLI half of this issue and explicitly scoped the dashboard half out because it needs a filter-faithful matching-session count from the sessions table, not a usage-row count. Current summary assembly still has that exact gap: the dashboard only receives totals plus usage-row-gated session counts, so it cannot tell the difference between unsupported Copilot usage and a truly empty filter result. This change adds that missing cross-backend session-presence helper behind the shared store interface, then exposes one narrow machine-readable summary signal only when the filter is Copilot-only, the totals still have no token data, and matching sessions do exist. The frontend uses that signal to render a short note above the summary cards and leaves the existing charts, tables, and generic empty states unchanged.

Scope stays tight. There are no CLI changes here, because kenn-io#934 already shipped them, and the separate model-picker complaint in the issue remains out of scope because it is a catalog-population problem, not a usage-visibility problem. Reviewers should look first at the new matching-session helper and its backend parity tests, then at the small summary-contract addition that the dashboard consumes.

Fixes kenn-io#349


Co-authored-by: Rod Boev <rodboev@users.noreply.github.com>
The usage dashboard has enough aggregate data to show which models and projects cost the most, but it does not let users compare two choices directly. Issue kenn-io#361 asks for questions like model A versus model B cost per session or token profile, which currently requires manual filtering and side-by-side arithmetic outside AgentsView.

This adds a pairwise usage comparison endpoint and dashboard panel. The comparison logic lives in the service layer so the direct backend, HTTP backend, server route, and frontend all use one metric definition. Each side applies the same usage filters and the same model or project filter path as the existing summary endpoint, so timezone handling, deduplication, pricing, and eligibility stay aligned with the rest of the usage dashboard.

The UI is additive: existing summary cards, time series, attribution, top sessions, and cache efficiency remain in place. The new panel focuses on model and project comparisons first, with cost, token, and session-based metrics returned by the backend instead of recomputed in the browser.

Fixes kenn-io#361


Co-authored-by: Rod Boev <rodboev@users.noreply.github.com>
…enn-io#949)

parse-diff v1 (kenn-io#662) rejected Warp, Forge, and Piebald because their sync phases had woven-in change detectors and no `DiscoverFunc`. The provider facade (kenn-io#876kenn-io#885, kenn-io#924) made that reason obsolete — all three now have a unified `Discover()` via `dbBackedProviderFactory` — but both parse-diff gates still keyed on `FileBased`, which is false for these shared-SQLite stores.

This relaxes the two gates (`parseDiffAgentDiscoverable`, `parseDiffAgentSupported`) to admit provider-authoritative agents with registered factories, instead of flipping `FileBased`: that flag is read at nine-plus other sites (watcher, sync engine, token accounting, remote sync, SSH resolve, settings) and genuinely means "reads a literal per-session file", so flipping it would change unrelated behavior.

Two hazards the wider gate exposed are handled in the same change:

- Virtual `<db>#<sessionID>` sources cannot be stat'd, so the raced-skew reclassification from kenn-io#805 would have masked every real drift on these agents as "raced". `stripVirtualSourceSuffix` now knows their DB filenames and `parseDiffSourceReliableForRaced` re-requires `FileBased`, so DB-backed agents fail closed toward reporting a change rather than masking one.
- These providers discover one source per session (like OpenCode, unlike per-DB Kiro), so `--limit` presence-keying moved to a shared per-session base list; sessions cut by `--limit` now report "not sampled" instead of false-positive presence findings.

Quack (kenn-io#930) is out of scope: it is the DuckDB remote-sync transport, not a registry agent with session sources to re-parse.

`--limit` ordering: the provider stamps each session's real mtime onto an additive, advisory `SourceRef.DiscoveryMTimeNS` while `Discover()` already has the store's session metas in hand, and the parse-diff sampler prefers it over the failed-stat fallback — so limited runs sample these virtual sources newest-first like file-based agents. The field is ordering-only metadata; skip-cache and data-version freshness still resolve through `Fingerprint`, and the raced-guard is unchanged.

Where to look: `internal/sync/parsediff.go` (gate, raced predicate, per-session base keying) and `internal/sync/parsediff_dbbacked_test.go` for the end-to-end coverage.



Co-authored-by: Matthew Jacobs <mjacobs@users.noreply.github.com>
…enn-io#948)

kenn-io#866 started surfacing parser anomaly signals in the CLI sync summary; the per-session `parser_malformed_lines` count has been serialized in the session API since then, but nothing in the UI rendered it. This adds the missing read side: a warning badge in the session-detail breadcrumb, shown only when the count is non-zero, with a tooltip explaining that N lines in the source file could not be parsed.

The badge reuses the local badge vocabulary already in `SessionBreadcrumb.svelte` — it sits next to the summary-mode badge from kenn-io#914 and follows the same conditional-pill pattern — rather than introducing a new shared component. The hand-maintained `Session` type gains the already-serialized field, and plural-aware message keys are added for en, zh-CN, and zh-TW.

Frontend-only; no backend or schema changes. Scope is deliberately the session-detail surface — the session list is untouched.

Where to look: `SessionBreadcrumb.svelte` for the badge and its gating, `core.ts` for the type addition.


Co-authored-by: Matthew Jacobs <mjacobs@users.noreply.github.com>
After rebasing on the upstream dashboard fix in kenn-io#947, the remaining change here is the CLI wording from kenn-io#934. That note still described GitHub Copilot as billing via a monthly request quota, but Copilot billing has moved on and that claim is now too specific for what agentsview can infer from local records.

The CLI still warns when an all-Copilot `usage daily --agent ...` filter returns zero token and cost totals, but the message now describes only the data limitation agentsview observes: these Copilot records do not include token or cost data that can be totaled. That keeps the user-facing explanation accurate without trying to summarize GitHub's current billing model.

Scope is intentionally small. The dashboard behavior is already handled by kenn-io#947 through the `unsupportedUsage` summary signal, so this PR no longer changes frontend copy, storage interfaces, or cross-backend session counting. Reviewers should look at `cmd/agentsview/usage.go` and the matching assertions in `cmd/agentsview/usage_test.go`.

Follow-up to kenn-io#934 and kenn-io#947.

Co-authored-by: Matthew Jacobs <mjacobs@users.noreply.github.com>
…enn-io#953)

Antigravity is decoded by reverse-engineered heuristics, and kenn-io#862 started recording which schema produced each session: `sessions.source_version` carries either a known release-range label ("1.0.7-1.0.10") or an `agy-schema:<12hex>` marker for fingerprints the parser has never seen. An unrecognized marker means a newer Antigravity build changed the schema and the heuristics may be silently wrong — but nothing surfaced it. This PR turns that stored signal into a visible one.

A single classifier, `parser.DecodeConfidence(agent, sourceVersion)`, derives a confidence for the two Antigravity agents: `low` for the unknown-schema marker, `high` for a recognized label, nothing otherwise. It is deliberately agent-gated because `source_version` is a generic field — Piebald and CommandCode write their own values there — so non-empty alone proves nothing. This is a separate axis from `transcript_fidelity` (kenn-io#914): fidelity measures sidecar coverage, confidence measures schema recognition; a session can be full-fidelity yet low-confidence and vice versa.

The value is computed at read time in `SessionDetail.MarshalJSON` rather than persisted. Confidence is a pure function of an already-stored column, so a persisted copy would refresh on exactly the same re-parse events while costing a dataVersion bump, a three-backend migration, and duplicate parse-diff drift whenever the schema map learns a new fingerprint. Derive-on-read gets identical freshness for none of that, and works unchanged across SQLite, PostgreSQL, and DuckDB read paths.

Three surfaces consume it: a warning badge in the session-detail breadcrumb (only for `low`; localized en/zh-CN/zh-TW), an `agentsview doctor` count of sessions on unrecognized schemas (folded into the existing Antigravity session scan, one extra aggregate rather than a second query), and a per-run `UnknownSchemaSessionsByAgent` counter in the sync anomaly summary so a new Antigravity release becomes visible on the first sync after it appears.

One test-visible consequence: the sync integration fixtures build minimal synthetic Antigravity databases, which are legitimately unrecognized schemas, so their expected `SyncStats` now include the anomaly count.

Where to look: `DecodeConfidence` in `internal/parser/antigravity_version.go`, the `MarshalJSON` hook in `internal/service/service.go`, and the recording seam in `internal/sync/engine.go`.


Co-authored-by: Matthew Jacobs <mjacobs@users.noreply.github.com>
Pushing to a Quack-backed DuckDB was broken in layers: schema setup issued `CREATE INDEX` against proxy tables and failed instantly; one poisoned row (a NUL byte in stored tool output) aborted the entire single-transaction push, forever; every statement paid its own HTTPS round trip (a measured 15,000x below line rate); and any server restart permanently stranded every connected client. This PR makes Quack push and serve correct first, then fast.

**Correctness**

- Schema setup, status/store reads, and mutations now use call shapes Quack attachments can actually execute; index and migration work runs only on the real server database.
- NUL bytes are stripped from persisted tool result content at ingest, with a data-version bump so existing rows are re-ingested. DuckDB VARCHARs cannot hold them, and one such row poisoned every push.
- Session batches commit in explicit server-side transactions. A failed batch retries per session; a persistently failing session is skipped and counted instead of aborting the run. Context cancellation stays fatal, and the sync watermark only advances on clean runs.
- Clients recover from server restarts: stale or detached attachments are detected and re-attached on one pinned connection, with a single retry for self-contained calls. Previously every client failed with `Invalid connection id` until process restart. Attach errors scrub credentials.

**Performance**

- Batches coalesce into one multi-statement `BEGIN`/`COMMIT` `query()` call instead of one round trip per statement.
- Batches are bounded by rendered bytes (2 MiB) so each POST executes within the Quack client's fixed 120 s deadline on small servers. Oversized single sessions leave the coalesced path and execute rendered mutation groups inside one remote transaction, preserving session atomicity without returning to per-row round trips.
- Adjacent single-row INSERTs render as bounded multi-row `VALUES` statements (~10x less server-side parse/dispatch), and tool rows push as read-free delete+insert instead of per-row existence checks that previously cost hundreds of thousands of serial reads per catch-up.
- A timed-out coalesced batch may still be executing server-side, so timeouts back off and retry once instead of immediately grinding per-session retries against the abandoned writer.

**Tradeoffs**

- The 120 s HTTP deadline is hard-coded in the Quack extension (its ATTACH `TIMEOUT` option is ignored), so this PR sizes normal coalesced work under the deadline rather than raising it.
- Oversized sessions can still be slower than normal coalesced batches, but they keep delete-and-replace work atomic while using rendered INSERT groups to avoid per-row round trips.
- The per-session retry path deliberately keeps statement-by-statement execution so a failing session still reports the exact statement that rejected it.

Core changes in `internal/duckdb/{sync,push,connect}.go`; real-server coverage, including restart recovery and rollback behavior, in `internal/duckdb/quack_smoke_duckdbtest_test.go`.


Co-authored-by: Phillip Cloud <cpcloud@users.noreply.github.com>
…nn-io#960)

The follow-up from PR kenn-io#947 is that the no-token-data usage hint and Copilot AI-credit accounting both depend on Copilot name matching in `internal/db/usage.go`. That helper now carries two separate meanings: whether an agent records per-message token data, and whether its cost is denominated in Copilot-style AI credits. Wes called out that a future Copilot variant or another no-token-data agent would have to remember this storage-layer helper, while the deeper home is capability metadata on the agent registry.

This moves the usage semantics onto `internal/parser/types.go`, with separate capability flags for agents that do not record per-message token data and agents whose cost should be surfaced as AI credits. The CLI no-token-data note and dashboard unsupported-usage trigger now ask the parser capability surface whether the selected filter contains only no-token-data agents, while the SQLite, PostgreSQL, and DuckDB accounting paths ask the distinct AI-credit capability before converting cost. Current behavior for Copilot, VS Code Copilot, and Visual Studio Copilot is preserved, but future agents can opt into one capability without inheriting the other.

The dashboard also gets a generic localized fallback for unknown `unsupportedUsage.kind` values across English, Simplified Chinese, and Traditional Chinese. The existing `copilot-no-token-data` copy stays scoped to Copilot-family filters, while other no-token agents now fall back to generic messaging instead of inheriting Copilot-specific wording, and future backend kinds still degrade visibly instead of rendering nothing. Scope stays limited to the ownership and fallback issues from kenn-io#947 (comment).


Co-authored-by: Rod Boev <rodboev@users.noreply.github.com>
wesm and others added 3 commits July 2, 2026 22:11
The 0.36.0 tag is cut, but the public docs still describe 0.35.2 as the newest release and do not explain the user-facing surfaces added alongside the mostly internal parser, usage, and sync work. This updates the release notes and the relevant docs pages so users can understand what changed without reading implementation PRs.

The release documentation calls out the narrow visible UI changes, the new API contracts for pairwise usage comparisons and branch tokens, and the parser-quality signals that now appear in session details and diagnostics. It also keeps contributor acknowledgements tied to the implementation history so the release page reflects who shipped the work.

Most of this release is backend and observability work, so the docs avoid promising new screenshot-led workflows where the product surface is intentionally small.

Co-authored-by: Wes McKinney <wesm@users.noreply.github.com>
Brings in upstream changes since the last sync at 12d3b03:
- docs: add 0.36.0 release notes (kenn-io#962)
- refactor(usage): derive unsupported usage from agent capabilities (kenn-io#960)
- Fix DuckDB push schema setup through Quack (kenn-io#945)
- feat(antigravity): flag sessions decoded from unrecognized schemas (kenn-io#953)
- fix(cli): avoid legacy Copilot billing wording (kenn-io#952)
- feat(frontend): show parser malformed-lines badge on session detail (kenn-io#948)
- feat(parse-diff): cover DB-backed Warp, Forge, and Piebald providers (kenn-io#949)
- feat(usage): add pairwise cost comparisons (kenn-io#925)
- fix(usage): surface unsupported Copilot usage filters (kenn-io#947)
- fix(test): keep a slow template checkpoint from poisoning the Windows suite (kenn-io#951)
- [codex] Prevent OpenCode WAL watcher feedback loop (kenn-io#956)
- feat(stats): add Cursor attribution parity (kenn-io#936)
- fix(db): backfill worktree mappings for empty cwd rows (kenn-io#946)
- perf: reduce daemon sync CPU on streaming sessions (kenn-io#954)
- feat(filter): add branch filter foundation (kenn-io#927)
cmd/agentsview/seedPricing writes the LiteLLM fallback
snapshot to model_pricing and fires a background multi-source
refresh, but it never wired the config-driven
[custom_model_pricing] map into the running DB. The CLI
statusline and pg serve paths call applyCustomPricing
explicitly, which masked the gap, but the embedded HTTP
server left db.customPricing at its zero value and silently
priced fork-private models at \$0.

Call applyCustomPricing immediately after seedPricing so
fork owners can configure their internal-model rates in
config.toml and have them flow into every GetDailyUsage
call without a CLI detour.

The read path was already correct: loadPricingMap merges
db.customPricing on top of whatever model_pricing returns, so
this fix is purely about plumbing the writer.
@godlockin
godlockin merged commit f745736 into main Jul 3, 2026
14 of 16 checks passed
@godlockin
godlockin deleted the feat/apple-silicon-local branch July 3, 2026 13:02
mjacobs pushed a commit that referenced this pull request Jul 24, 2026
Restore synchronous startup fallback seeding while keeping the initial network refresh asynchronous, and remove the periodic custom-pricing map write that raced with request reads.

Track OpenRouter aliases so refreshes can remove obsolete bare names locally and in PostgreSQL, preserve ordered LiteLLM precedence, and accept free-model zero prices.

VALID (fixed): #1, #2, #3, kenn-io#4, kenn-io#5
INVALID (dismissed): none
PEDANTIC (skipped): none
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

10 participants