Skip to content

Issues #5–#11: provider-agnostic auth, expert tools, multi-ref provenance, session naming (v0.2.2) - #12

Merged
lowlorenz merged 17 commits into
masterfrom
feat/issues-5-11
Jun 25, 2026
Merged

Issues #5–#11: provider-agnostic auth, expert tools, multi-ref provenance, session naming (v0.2.2)#12
lowlorenz merged 17 commits into
masterfrom
feat/issues-5-11

Conversation

@lowlorenz

Copy link
Copy Markdown
Contributor

Resolves the #5#11 milestone and prepares the 0.2.2 release. Each issue was re-verified resolved by an adversarial review pass, and the confirmed findings from that pass are folded in.

Features

Pre-release hardening (from the adversarial review)

  • Read-only experts can no longer read .chronos/.env (provider keys) via read_file/list_dir/grep.
  • Mid-flight expert abort no longer recorded as a successful turn.
  • Column-resize/splitter drags use pointer capture (no leaked listener on pointer-up outside the webview).
  • Auto-start gated on a reconciled bootstrap (never fires an unsolicited pi install); in-flight start guard.
  • Credential files (.env, auth.json) written 0o600; dev settings marked scope: machine.
  • Helper-model ladder prefers current small tiers / rolling aliases over retired "cheapest"; misc bbox-bounds and label fixes.

Release

  • Both packages bumped to 0.2.2 (vscode lockfile resynced from a stale 0.2.0).
  • Note: ensureBootstrap pins the pi-package to the v<version> tag, so a v0.2.2 tag is required after merge.

Build green: chronos tsc · host tsc · webview tsc · esbuild · RPC contract canary.

Closes #5
Closes #6
Closes #7
Closes #8
Closes #9
Closes #10
Closes #11

🤖 Generated with Claude Code

Lorenz Hufe and others added 17 commits June 23, 2026 09:21
#7, #11)

#11: vision-expert subagents (task/task_batch) now run a bounded agentic loop
instead of a single completion. They can call `view_region` (zoom into a
normalized bbox at full resolution) and `view_page` (load another page from the
source) to pull in detail on demand, rather than depending on the orchestrator
to guess the right crop up front. The loop is capped at 8 tool calls; the full
exchange (tool calls + results) is kept in the session and persisted as `steps`,
with tool-result images re-cropped from disk on restore (no base64 bloat). Tools
are only offered to models that can consume images, and the loop honors the
caller's abort signal (threaded through task/task_batch).

#7: removes the hardcoded `google/gemini-3-flash-preview` default. When no model
is specified, experts fall back to the orchestrator's current model (whatever the
user has selected/authed in pi) — no provider is baked in. Model recommendations
move to the docs. Prompts/tool descriptions are de-Geminified and document the
new self-zoom tools.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
On agent_end the pi-package summarises the session's USER prompts (only — never
assistant replies, tool results, or page images) into a short title using the
cheapest model pi has auth for, and caches it in a `.chronos/session-names.json`
sidecar keyed by session id. The title is refined over the first few prompts then
locked, with an in-process in-flight guard and atomic (temp+rename) writes.

Best-effort and provider-agnostic: it falls back silently to the existing
first-message truncation if no model is configured, the user is offline, or the
call fails — naming never blocks the history list. The host overlays this name
only when the user hasn't set one explicitly (precedence: user name > generated >
first-message truncation).

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
…mes (#9, #8)

#9: the "N messages" label in the history drawer now counts only USER prompts,
not assistant/tool messages (which an agent that fans out many expert turns per
prompt inflates meaninglessly). The cheap prefix scan still pre-filters by the
`"role":"user"` substring, but now confirms the parsed role so a tool/assistant
line that merely embeds that text isn't miscounted.

#8 (host side): the session scan overlays the pi-package's auto-generated name
(.chronos/session-names.json) as the display-name fallback, read fresh per
listing so a late-written name isn't masked by the per-file mtime cache.

Also extends the webview protocol (ChronosSessionInfo gains sessionId/userName;
new loginRequired / viewer/clearSource / login messages used by #5/#6/#10).

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
…sion reset (#6, #5, #10)

These three land together because they co-edit the panel, app, data-viewer, and
styles.

#6 — first-class login: pi's /login is TUI-only and unusable over RPC, so when no
models are available the panel shows a discoverable CTA (header "Log in" button +
empty-state banner). It opens a provider picker + API-key input, writes
<PROVIDER>_API_KEY to .chronos/.env, and restarts the agent. Provider-agnostic;
also exposed as the "Chronos: Connect AI Provider" command. Init Workspace no
longer prompts for a Gemini key (#7), and the .env reader accepts mixed-case
custom variable names.

#5 — multi-reference provenance: a data row may cite several (page, bbox, source)
locations via list-valued chronos_page/chronos_bbox/chronos_source; the viewer
renders one citation chip per reference. Scalars still work (treated as a single
reference) and a length-1 list broadcasts. Lists align by index, and an invalid
page in the middle of a list no longer shifts the alignment of the others.

#10 — new-session reset: on a fresh session the host clears the viewer + source
dropdown (viewer/clearSource) so the displayed state matches the (unbound)
selection, re-arming the Data-tab refresh guard and dropping a late preview that
resolves after the clear.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
…ulti-reference rows (#5, #6, #7, #11)

- #7/#6: drop "Gemini API key required" framing across README, the extension
  README, and DOCS; document the native "Log in" flow and the standard
  per-provider env vars. Model choice is the user's; recommendations (cheap model
  for routine pages, stronger for hard ones) now live in the docs, not in code.
- #11: document the expert self-zoom tools (view_region / view_page) and the
  bounded tool loop.
- #5: document list-valued provenance keys (multiple references per row) with
  examples and the index-alignment / broadcast rules.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
…in (#6)

Extends the panel login beyond API keys: the "Log in" picker now offers
"Anthropic — Claude Pro/Max (subscription)", which runs the OAuth flow directly
from the host (pi's own /login is TUI-only and unreachable over RPC).

A self-contained anthropic-oauth.ts module (no pi runtime dependency — constants
copied verbatim from pi-ai, same as rpc-types.ts) does PKCE + authorize URL +
the localhost callback server + token exchange. The flow opens the browser,
auto-captures the redirect on localhost:53692 (with a paste-the-code fallback if
the browser is remote or the port is busy), exchanges the code for tokens, and
writes the credential to ~/.pi/agent/auth.json in pi's exact shape
({anthropic:{type:"oauth",...}}) — so the running pi reads and refreshes it.
Then it restarts the agent and models populate.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Experts now run a tool loop (view_region/view_page), but that activity was
invisible — the drawer only showed prompt/response. Now each runExpertTurn
returns a compact `toolUses` summary (tool + page + bbox + isError), the
task/task_batch result carries it in `details`, and the expert drawer renders
each as an "examined" step. Region/page steps are clickable viewer links
(reusing the data-page/data-bbox view-link path), so the historian can jump to
exactly what the expert pulled in — full oversight of what the task did. Works
live and on resumed sessions (details survive history). Guarded by a new UI
test asserting the drawer renders the tool-use links.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
…#11)

Expert subagents now have a real capability model:

- Read-only by default: view_region, view_page, read_file, list_dir, grep —
  the file tools are scoped to the workspace root (path-escape rejected).
- The orchestrator can elevate a single task/task_batch call with
  grant: ["bash"|"write"|"edit"], adding bash / write_file / edit_file. This is
  OFF by default for oversight and safety, and requesting it triggers a user
  confirmation before any expert runs (once per task, once per batch cohort);
  denial aborts the call. executeExpertTool also refuses any elevated tool whose
  capability wasn't granted (defense in depth).

Tool definitions are now built per-turn from (vision capability + granted caps),
each call is captured with a short detail (command/path/term) and surfaced in the
expert drawer: page steps stay clickable viewer links, file/search/command steps
show as chips, and bash/write/edit are visually flagged so elevated actions are
easy to audit. Prompts/docs explain that experts are read-only by default and
that grants need user consent.

Verified: agent-side integration test (15 cases incl. capability denial, path
sandboxing, bash exec, write/edit) + the UI suite asserting the drawer renders
links, chips, and the flagged elevated action.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Split machine-level dependency install out of "Init Workspace" into its own
chronos.setup command, and add a Getting Started walkthrough so onboarding is
discoverable and each step's scope is distinct.

- chronos.setup ("Install Dependencies") wraps ensureBootstrap; chronos.init no
  longer installs deps (it only scaffolds the workspace); startSession keeps its
  lazy gate.
- Install runs as an awaited VS Code task (runSetupTask) instead of a
  fire-and-forget terminal, so the flow auto-continues instead of telling the
  user to re-run the command.
- Getting Started walkthrough (4 steps) with context-key-driven completion;
  auto-opens once on first run when pi is missing. Activation moved to
  onStartupFinished; HttpServer now binds lazily so startup has no side effects.
- Unify pi-binary detection with the launcher: extract resolvePiBin + hasPi into
  pi-env.ts so "is pi present?" matches "how pi is launched" (fixes false
  negatives when pi isn't on GUI VS Code's PATH).
- Dev overrides: chronos.piPackageSource (install the pi-package from a local
  path or branch) and chronos.piNpmPackage; default pi CLI package is the
  maintained @earendil-works/pi-coding-agent.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
@mariozechner/pi-{coding-agent,ai} are deprecated (frozen at 0.73.x); the
maintained packages are @earendil-works/pi-{coding-agent,ai} (same author, a
scope rename). Move the pi-package's build off the deprecated scope.

- Rename both peer deps and all 17 import specifiers across tools/ and utils/;
  reinstall (lockfile updated).
- Reconcile event handlers with the 0.79 API: session_switch folded into
  session_start (now carries `reason`); the session_directory hook is removed
  (pi >=0.7x dropped it -- PI_CODING_AGENT_SESSION_DIR replaces it). Both hooks
  were already non-firing on the 0.79 runtime.
- Update CLAUDE.md and README to match.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Generate session titles with a small, current model chosen for the
orchestrator's provider instead of the registry's blind "cheapest", which
ranks decommissioned models first and 404s at call time.

helperModelCandidates() returns ordered specs best-first: the orchestrator
provider's small tier (haiku/flash/nano), then any auth'd small-tier model
from any provider, then the cheapest, then the orchestrator's own model;
generateSessionTitle tries each until one succeeds. Hardening from the
pre-release review:
- rank rolling "*-latest" aliases first (they have no version digit but are
  always the currently-served model);
- when no orchestrator is known, still prefer an auth'd small-tier model over
  the possibly-retired global cheapest;
- defensive cost accessor so a cost-less custom-provider model can't throw the
  comparator out of the (try/catch-free) candidate builder;
- correct the saveSessionName JSDoc, which overstated cross-process write
  safety (atomic publish only; same-workspace races self-heal on next agent_end).

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Pre-release review fixes to the vision-expert tool loop:

- read_file/list_dir/grep are always-on (no human gate) but resolveInWorkspace
  only blocked path escapes, so a default read-only expert could read
  .chronos/.env (provider API keys) and echo them into the transcript. Apply
  walkFiles' policy (dot-dirs + SKIP_DIRS off-limits) to every path-based tool,
  and filter those entries out of list_dir.
- A cancel that lands while complete() is in flight resolves with an "aborted"
  response rather than throwing; it was committed to the session, persisted,
  and returned ok:true — so callers wrote "(empty response)" to output files
  and the turn was dropped on restore. Treat aborted like a failed turn before
  pushing/persisting.
- Clamp the expert's view_region bbox into bounds in bboxToPixels: the
  self-zoom bbox isn't schema-validated at call time, so an out-of-range value
  produced a negative/out-of-bounds extract that made sharp throw.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
…#5)

Drag a column header's right-edge handle to resize; double-click to auto-fit
(canvas text measure). The first interaction seeds every column's rendered
width and flips the table to fixed layout, so untouched columns hold and the
table can grow past the viewport with horizontal scroll.

Includes pre-release review fixes:
- Use pointer capture on drag handles (column resize + preview splitter) so a
  pointerup released outside the webview still tears down the move listener;
  without it the listener leaked and the column stuck to the cursor with no
  recovery short of reload.
- Prune colWidths to the live column set on in-place reload (Refresh), so a
  schema change doesn't leave orphan width keys or silently revert to auto
  layout on a renamed column.
- Multi-reference provenance (#5): a malformed flat numeric bbox (length != 4)
  now collapses to a single null instead of inflating the chip count, and a
  lone bbox is no longer broadcast across distinct pages (only across regions
  of one page), which had mis-cropped the other pages.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
…e dev settings

Auto-open the panel and start a session when a Chronos workspace (a folder with
.chronos/) is opened. Opt out with chronos.autoStartSession.

Gated and hardened so the launch is never a surprise:
- Fire only when the bootstrap is already reconciled (deps present AND at the
  ref this extension wants), not merely deps-present. Otherwise an extension
  upgrade (stale stored ref) would auto-fire an unsolicited `pi install`
  terminal on every startup, re-firing on each launch if the install failed.
  A stale / un-set-up workspace falls back to the manual command / walkthrough.
- Serialise chronos.startSession with an in-flight guard so auto-start racing a
  walkthrough/manual click can't construct two panels + pi subprocesses before
  ChronosPanel.current is assigned.

Mark chronos.piPath / piPackageSource / piNpmPackage as "scope": "machine" so a
malicious workspace .vscode/settings.json can't supply values that get
interpolated into the setup ShellExecution (the install path is reachable
without a modal for an already-installed returning user).

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
…ion label

Pre-release review fixes around the new login flow and UI:
- Write .chronos/.env and ~/.pi/agent/auth.json owner-only (0o600) and chmod
  after writing (writeFileSync's mode only applies on creation), and create the
  agent dir 0o700 — mirroring pi's auth-storage hardening. These hold
  billing-bearing API keys / long-lived OAuth tokens.
- Use pointer capture on the main app splitter (same leak class as the
  data-viewer handles).
- Generalise the login button/banner copy: it claimed "saves an API key to
  .chronos/.env", wrong for the Anthropic subscription OAuth path (which writes
  auth.json, no API key).
- Skip the synthetic "Source selected: …" follow-up when picking a session's
  first-message label, so it never surfaces as the history label (the
  agent-side namer already skips it).

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Bump both packages to 0.2.2 for the release. Also resync the chronos-vscode
lockfile, whose own version had drifted to 0.2.0.

Note: ensureBootstrap pins the pi-package install to the `v<version>` git tag,
so cutting the release requires a `v0.2.2` tag on the repo.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
The UI test injects its mock pi via a workspace-scoped chronos.piPath in the
fixture's .vscode/settings.json (test/run-ui-test.mjs); machine scope made
VS Code ignore that workspace value, so the extension fell back to "pi" on PATH
and the agent failed to spawn (ENOENT). piPath wasn't one of the shell-
interpolation vectors the review flagged — only piPackageSource / piNpmPackage
are (those stay machine-scoped). Restore piPath to its prior window scope.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
@lowlorenz
lowlorenz merged commit 6efe817 into master Jun 25, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment