Skip to content

Commit f565a97

Browse files
committed
Release v1.24.0 — sanitized source snapshot (2026-07-24)
1 parent 91c0d49 commit f565a97

278 files changed

Lines changed: 11331 additions & 555 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.golangci.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,10 @@ issues:
6464
- testdata
6565
- bin
6666
- scripts
67+
# Vendored npm packages can ship Go source (e.g. flatted's golang/ port
68+
# under vscode/node_modules); third-party code isn't ours to lint and CI
69+
# never installs node_modules for the Go job.
70+
- node_modules
6771
# oapi-codegen output (DO NOT EDIT). Declaring security schemes used only
6872
# by documentation-tagged operations emits unused context-key types that
6973
# `unused` would flag; generated code is regenerated deterministically and

CHANGELOG.md

Lines changed: 73 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,79 @@
22

33
All notable changes to SuperBased Observer are documented here.
44

5-
## [Unreleased]
5+
## [1.24.0] — 2026-07-24
6+
7+
### Added
8+
9+
- **Session Cockpit: a live per-terminal floating panel.** Every embedded
10+
dashboard terminal running an AI tool gains a "⊙ Session" button that
11+
opens a compact, draggable cockpit next to the terminal — the glanceable
12+
companion to the session-detail page, not a clone of it. It shows, live:
13+
a now strip (last activity, tokens/sec with an honest measured/estimated
14+
basis badge, live process count), total cost with the AI/tool split and
15+
the next-message cost band, context fill against the model budget, token
16+
buckets, the 5h/7d rate-limit gauge (proxy-routed sessions), prompt-cache
17+
expiry countdown chips, system telemetry (CPU/memory/disk sparklines and
18+
the spawned-process tree from process observation, plus proxied API
19+
traffic with byte totals when body capture measures them), and the last
20+
five turns deep-linking into the session-detail page.
21+
- **Terminal→session live linkage.** New `GET /api/terminal/session/<token>`
22+
resolves a running terminal to its correlated observer session (id +
23+
correlation confidence, refusal-ordered so remote callers can never probe
24+
token existence); the cockpit keeps polling so a later authoritative
25+
correlation re-points the panel, and links below the out-of-band tier
26+
carry an explicit "≈ linked" badge.
27+
- **One-click process-capture enable.** When process observation is off, the
28+
cockpit's System section explains what's missing and offers an atomic
29+
server-side enable (`POST /api/process/enable-capture`): non-runnable
30+
backend selections switch to automatic with the previous value named in
31+
the notice, hosts with no runnable capture backend get an honest
32+
"unavailable on this machine" instead of a false success, and capture
33+
honestly starts only after the daemon restarts.
34+
- **`?tail=N` on the session messages API** — returns the true last N rows
35+
of the full timeline for fast pollers (explicitly rejected when combined
36+
with pagination parameters).
37+
- **`?summary=1` on the session network API** — a server-side aggregate
38+
separating proxied API calls (with request/response byte sums from body
39+
metadata) from OS-observed process connections.
40+
41+
- **Tool-binary resolution with classified verdicts.** Every embedded-
42+
terminal launcher now resolves its tool binary through a shared ladder —
43+
process PATH, then a memoized login-shell PATH capture, then native
44+
install-location probing, then (WSL only) a foreign-Windows-home check —
45+
and classifies the result as `ok`, `ok_off_path`, `shadowed`,
46+
`foreign_only`, or `not_found`. Fixes the class of bug where a
47+
Windows-installed npm shim shadowed (or stood in for) a native binary on
48+
WSL, and the launcher's stale process PATH meant a fresh native install
49+
wasn't picked up without restarting the daemon.
50+
- **Guided one-click install.** When a tool isn't natively resolvable, the
51+
New Terminal dialog shows the verified official install command and an
52+
"Install in terminal" button that runs it in a visible PTY. Gated by the
53+
new `[terminal.launch].allow_install` config key (default on).
54+
- **`observer doctor` and `observer adapters` surface binary-resolution
55+
health**, including a `foreign_only` bucket (tool installed on Windows
56+
only, not launchable from a WSL daemon) and a best-effort Windows
57+
proxy-route reachability check.
58+
- **`observer init` writes Windows-side proxy routes.** When the daemon
59+
runs in WSL, `init` can now also point a Windows-installed claude-code or
60+
codex at the daemon's proxy (`localhost:8820`, relying on WSL2's
61+
`localhostForwarding`), so accurate token capture works even when the AI
62+
tool itself is installed on the Windows side.
63+
- **`[launch.tools.<tool>].path`** config override to pin a specific binary
64+
path per tool, bypassing the resolution ladder.
65+
- Custom terminal project paths typed in Windows form (`C:\Users\…`) are
66+
now translated to their WSL mount equivalent before validation.
67+
68+
### Fixed
69+
70+
- **Concurrent dashboard config saves can no longer lose updates.** All
71+
in-process config read-modify-write paths — section saves, pricing,
72+
backup restore, remote-manage toggles, terminal limits/policy,
73+
experiments, and the admission-policy persister — now serialize on one
74+
shared lock in the config package (cross-process CLI writes remain
75+
outside a mutex's reach and are documented as such).
76+
- The attach-replay bound test no longer flakes on slow runners (the
77+
precondition now waits for the pump to drain the full produced stream).
678

779
## [1.23.0] — 2026-07-23
880

browser-extension/manifest.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"manifest_version": 3,
33
"name": "SuperBased Observer — Browser Capture",
4-
"version": "1.23.0",
4+
"version": "1.24.0",
55
"description": "Opt-in, passive observability of your own AI chatbot web usage (ChatGPT, Claude, Perplexity, Gemini, Copilot). Captures per-turn metadata + estimated tokens locally; nothing leaves your machine except to your local observer daemon.",
66
"minimum_chrome_version": "111",
77
"permissions": [

cmd/observer/admission_persist.go

Lines changed: 25 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -69,19 +69,31 @@ func admissionPolicyPersister() func(ctx context.Context, policyJSON []byte) err
6969
if err := json.Unmarshal(policyJSON, &p); err != nil {
7070
return fmt.Errorf("admissionPolicyPersister: decode policy: %w", err)
7171
}
72-
path, err := resolveAdmissionConfigPath("")
73-
if err != nil {
74-
return fmt.Errorf("admissionPolicyPersister: resolve config path: %w", err)
75-
}
76-
cfg, err := loadConfigForSetup(path)
77-
if err != nil {
78-
return fmt.Errorf("admissionPolicyPersister: load config: %w", err)
79-
}
80-
cfg = applyAdmissionEditorPolicy(cfg, p)
81-
if err := config.WriteToml(path, cfg); err != nil {
82-
return fmt.Errorf("admissionPolicyPersister: write config: %w", err)
83-
}
84-
return nil
72+
// Serialize the WHOLE resolve→load→overlay→write span against every
73+
// other in-process config writer — critically the dashboard's config
74+
// handlers, which hold the SAME process-wide mutex via
75+
// config.WriteLock(). This daemon runs the dashboard and this persister
76+
// in one process; without the shared lock a section save and a policy
77+
// persist can each read the same on-disk base and clobber the other's
78+
// edit (lost update in BOTH directions). config.WithConfigLock is the
79+
// one seam (CLAUDE.md #4 — one owner per piece of state). Cross-PROCESS
80+
// CLI writers remain unserialized — an accepted limitation documented on
81+
// WithConfigLock (flock is the future upgrade).
82+
return config.WithConfigLock(func() error {
83+
path, err := resolveAdmissionConfigPath("")
84+
if err != nil {
85+
return fmt.Errorf("admissionPolicyPersister: resolve config path: %w", err)
86+
}
87+
cfg, err := loadConfigForSetup(path)
88+
if err != nil {
89+
return fmt.Errorf("admissionPolicyPersister: load config: %w", err)
90+
}
91+
cfg = applyAdmissionEditorPolicy(cfg, p)
92+
if err := config.WriteToml(path, cfg); err != nil {
93+
return fmt.Errorf("admissionPolicyPersister: write config: %w", err)
94+
}
95+
return nil
96+
})
8597
}
8698
}
8799

cmd/observer/antigravity.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ func newAntigravityCmd() *cobra.Command {
4646
"to separate observer flags from agy flags. NEVER touches API keys.",
4747
SilenceErrors: true,
4848
RunE: func(cmd *cobra.Command, args []string) error {
49-
bin, err := resolveToolBin("agy", binPath, "--agy-path")
49+
bin, err := resolveToolBin("antigravity-cli", binPath, "--agy-path", configPath, cmd.ErrOrStderr())
5050
if err != nil {
5151
return err
5252
}

cmd/observer/benchmark_driver.go

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"encoding/json"
99
"errors"
1010
"fmt"
11+
"io"
1112
"os"
1213
"os/exec"
1314
"path/filepath"
@@ -346,15 +347,15 @@ func parseClaudeResult(out []byte) (string, bool) {
346347
return last, found
347348
}
348349

349-
// resolveClaudeBin returns the configured claude path or resolves `claude` on
350-
// PATH. A missing binary is a Drive-time harness error (no spend), not a
351-
// Preflight failure — Preflight owns only the isolated-daemon gate, so a
352-
// dry-run cost estimate still works on a host without claude installed.
350+
// resolveClaudeBin returns the configured claude path or resolves the claude
351+
// binary through the registry ladder (registry key "claude-code"). A missing
352+
// binary is a Drive-time harness error (no spend), not a Preflight failure —
353+
// Preflight owns only the isolated-daemon gate, so a dry-run cost estimate
354+
// still works on a host without claude installed. stderr is discarded (the
355+
// harness is non-interactive); any PATH-hygiene note is dropped, and an
356+
// unresolvable binary surfaces as the returned error at Drive time.
353357
func resolveClaudeBin(configured string) (string, error) {
354-
if configured != "" {
355-
return configured, nil
356-
}
357-
return exec.LookPath("claude")
358+
return resolveToolBin("claude-code", configured, "--claude-path", "", io.Discard)
358359
}
359360

360361
// defaultObserverDBPath returns the operator default observer DB path

cmd/observer/claude.go

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@ import (
2626
"fmt"
2727
"net"
2828
"os"
29-
"os/exec"
3029
"path/filepath"
3130
"strconv"
3231
"strings"
@@ -244,13 +243,9 @@ func runClaudeLauncher(ctx context.Context, opts claudeLauncherOptions) error {
244243
opts.resume)
245244
}
246245

247-
bin := opts.claudePath
248-
if bin == "" {
249-
resolved, lookErr := exec.LookPath("claude")
250-
if lookErr != nil {
251-
return fmt.Errorf("locate claude binary: %w (set --claude-path)", lookErr)
252-
}
253-
bin = resolved
246+
bin, binErr := resolveToolBin("claude-code", opts.claudePath, "--claude-path", opts.configPath, opts.stderr)
247+
if binErr != nil {
248+
return binErr
254249
}
255250

256251
// --verify is a NO-LAUNCH mode: evaluate it BEFORE any launch branch so it

cmd/observer/clinecli.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ func newClineCLICmd() *cobra.Command {
6565
return fmt.Errorf("load config: %w", err)
6666
}
6767
resolved := resolveProxyURL(cfg.Proxy.Port, proxyURL)
68-
bin, err := resolveToolBin("cline", binPath, "--cline-path")
68+
bin, err := resolveToolBin("cline-cli", binPath, "--cline-path", configPath, cmd.ErrOrStderr())
6969
if err != nil {
7070
return err
7171
}

cmd/observer/codex.go

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -410,13 +410,9 @@ func runCodexLauncher(ctx context.Context, opts codexLauncherOptions) error {
410410
// bounds; the caller reaches it only on a proxyRouteProceed / neutralized
411411
// verdict.
412412
func runCodexChild(ctx context.Context, opts codexLauncherOptions, proxyURL string, preflight []codexipc.Process) error {
413-
bin := opts.codexPath
414-
if bin == "" {
415-
resolved, lookErr := exec.LookPath("codex")
416-
if lookErr != nil {
417-
return fmt.Errorf("locate codex binary: %w (set --codex-path)", lookErr)
418-
}
419-
bin = resolved
413+
bin, binErr := resolveToolBin("codex", opts.codexPath, "--codex-path", opts.configPath, opts.stderr)
414+
if binErr != nil {
415+
return binErr
420416
}
421417

422418
codexArgs := opts.codexArgs

cmd/observer/copilotcli.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ func newCopilotCLICmd() *cobra.Command {
6060
return fmt.Errorf("load config: %w", err)
6161
}
6262
resolved := resolveProxyURL(cfg.Proxy.Port, proxyURL)
63-
bin, err := resolveToolBin("copilot", binPath, "--copilot-path")
63+
bin, err := resolveToolBin("copilot-cli", binPath, "--copilot-path", configPath, cmd.ErrOrStderr())
6464
if err != nil {
6565
return err
6666
}

0 commit comments

Comments
 (0)