feat(cline): read the Cline CLI's sessions layout in the cline provider - #891
Conversation
The Cline CLI (npm `cline`, 3.x) stores sessions as <sessions>/<id>/<id>.json + <id>.messages.json. The existing `cline` provider only discovers tasks/<id>/ui_messages.json, so every CLI session was silently reported as $0.00 — no warning, not even under --verbose. Adds `cline-cli` as its own provider rather than a third root on `cline`, leaving the shared Cline-family parser (Roo Code, KiloCode, IBM Bob) untouched. It mirrors the CLI's own root resolution (CLINE_SESSION_DATA_DIR -> CLINE_DATA_DIR -> CLINE_DIR -> ~/.cline), implements probeRoots() so `doctor` can tell "not installed" from "wrong override", emits one call per assistant message's `metrics` block, and falls back to the session rollup when a session carries none. The fallback reads `usage`, not `aggregateUsage`, which folds in spawned subagents that are themselves separate session directories. Two supporting changes, both required for CLI costs to report correctly: - parser.ts re-priced cline-cli calls from tokens because the provider was not on the reported-cost allowlist, inflating a real 12-session local sample from $1.11 to $3.92. - session-cache.ts gains the matching PROVIDER_ENV_VARS entry (so a changed override invalidates) and a `reported-cost-v1` parse version (so sessions cached before the allowlist fix re-parse once instead of being re-priced forever). Cost is treated as metered only when actually present and non-negative, so a metered $0 stays reported while a missing or negative cost falls back to token pricing — applied identically on the per-message and rollup paths. Timestamps promote a seconds-resolution value rather than silently landing in 1970, matching the guard kiro.ts uses. CLINE_DIR / CLINE_DATA_DIR / CLINE_SESSION_DATA_DIR are added to the test env-isolation list so a developer's real sessions cannot bleed into fixtures. The VS Code variant discovery bug reported alongside this in getagentseal#874 is deliberately NOT fixed here — it shipped in getagentseal#882. Verified against 18 real local sessions: 142 calls, 4,934,762 input / 224,561 output tokens, and a cost matching the CLI's own metered total to the cent. `codeburn doctor` reports "Cline CLI OK". Refs: getagentseal#874
56ec75d to
448d470
Compare
|
Maintainer note: the |
iamtoruk
left a comment
There was a problem hiding this comment.
You argued me out of my own call and you were right. The separate provider is the correct shape, and it matches what the repo already does for cursor/cursor-agent and kimi/kimicode. The clincher is the point you made: a merged provider would have coupled the cache, so every future CLI parse-version bump would re-parse extension sessions. Keeping cline-cli on its own key avoids that. Sorry for pointing you the other way first.
Reviewed and verified: cost matches the CLI's metered total to the cent on your real data, the metered vs estimated split is clean, and doctor now tells "not installed" apart from "broken root", which was the whole point of your report. One adversarial pass turned up a single edge case: if a session directory is duplicated on disk (same session_id), the rollup fallback can double-count it. Over-reports only and needs an abnormal layout, so not a blocker; filed as #894 with the repro and a one-line fix if you want to grab it. Merging. Thanks for the exact-to-the-cent writeup and for pushing back.
ozymandiashh
left a comment
There was a problem hiding this comment.
Reviewed in full, and verified locally since this repo's CI does not run the test suite: tsc --noEmit clean, 57/57 across providers/cline-cli.test.ts, provider-registry.test.ts and provider-turn-grouping.test.ts on the branch.
The provider itself is one of the cleaner ones in the tree: defensive record/string/number guards on every read, the presence-not-truthiness rule for metered cost (a $0 call stays reported instead of being re-estimated), the seconds-vs-milliseconds timestamp promotion mirroring kiro's guard, namespaced dedup keys, sibling-first resolution of the messages file over the stale recorded absolute path, and the rollup fallback deliberately reading usage instead of aggregateUsage to avoid double counting spawned subagents. The integration surface is exactly the three places it should be: the reported-cost allowlist in parser.ts, PROVIDER_ENV_VARS/PROVIDER_PARSE_VERSIONS in session-cache.ts, and registry membership.
On the shape question you addressed to the maintainers: separate provider is the right call, and I'll own that decision. The precedent argument is real (kimi/kimicode and cursor/cursor-agent are exactly this split, and I wrote one of those providers), the cache-key coupling argument is decisive (PROVIDER_PARSE_VERSIONS keyed by name means a merged provider turns every CLI parser bump into a forced re-parse for extension-only users), and the doctor legibility point is the original bug. Your own trade-off framing seals it: splitting later is cheap, merging later is a cache-invalidation event. If @iamtoruk wants the single-row presentation, a displayName grouping can deliver it without touching the provider split.
Merging. Thanks for a PR body that made the review about verification instead of archaeology.
Fixes the headline defect in #874: the Cline CLI's sessions layout was never scanned, so every CLI session reported $0.00 — silently, not even under
--verbose.Reworked to @iamtoruk's ruling (was a separate
cline-cliprovider, now folded intocline).Summary
sessions/<id>/layout as a third root of the existingclineprovider — no second provider, so Cline stays one row and the CLI root stays outside the task-id dedup pass.src/providers/cline-cli-parser.ts.vscode-cline-parser.tsis untouched — Roo Code, KiloCode and IBM Bob all ride on it.CLINE_SESSION_DATA_DIR→CLINE_DATA_DIR→CLINE_DIR→~/.cline.probeRoots()reports every root sodoctordistinguishes "not installed" from "override pointing elsewhere".How the two layouts stay apart
Task roots still dedupe among themselves by task id; the CLI root is discovered separately and appended, so a task and a session sharing an id both survive. Task discovery only ever reads
<root>/tasks/, so the fact that~/.cline/datais both a task root and the parent ofsessions/cannot double count — there is a test pinning that overlap.Sources are routed by matching a source file against its own parent directory (
<id>/<id>.json), not by a bare.jsonsuffix, so a task directory that happens to be namedweird.jsonstill goes to the task parser. Emitted calls carryprovider: 'cline'; dedup keys stay namespacedcline-cli:<sessionId>:<messageId>so they can never collide withcline:<taskId>:<index>.Two deliberate behavior notes
Metered cost is preserved without moving anyone's existing numbers. The CLI meters cost per message, so those calls set
costIsEstimated: falseand keep their reported cost instead of being re-priced from tokens. The pass-through inparser.tsis gated on that per-call flag rather than on the provider name — the extension path never sets it, so existing Cline users' dollar amounts are unchanged. (Worth flagging separately: extension-reported costs are currently discarded and re-priced, which looks like the same latent issue; I left it alone rather than move numbers you didn't ask me to move.)modelDisplayNamenow resolves names instead of passing ids through. The CLI records routed ids likecline-pass/glm-5.2, unreadable raw. Side effect: extension ids get their real labels too (claude-sonnet-4-5→Sonnet 4.5). That is a visible change for existing users, so it has an updated test rather than a silent flip.toolDisplayNamemaps only the CLI's tool vocabulary; no key in that table collides with a Cline VS Code tool name (read_filesvsread_file,search_codebasevssearch_files,ask_questionvsask_followup_questionare all distinct), so extension names pass through untouched.Testing
npm testpassesnpm run buildsucceedsFor new providers only
Not a new provider any more, but the same evidence bar applies:
npm run dev -- todayshows correct costs and session countsnpm run dev -- models --provider clineshows correct model names and pricingCorrectness against the source of truth — summing the
metricsblocks straight off disk versus what CodeBurn reports:Exact on calls and tokens; cost matches the CLI's own metered total to the cent.
codeburn doctor— one row, every root probed:npm run dev -- models --provider cline --period month:(The two ids still showing raw there are unrelated
SHORT_NAMESgaps, fixed separately in #893.)Test suite. Assertion failures are the meaningful signal on this machine, since a large share of the CLI-subprocess tests hit their 5s timeout under parallel load in any run, on
mainand here alike (23–24 timeouts either way). Assertion failures: 3 onmain, 3 here — the identical three (parser.test.ts×2 durable-orphan/copilot-purge,app/electron/cli.test.ts). A fourth,cli-durable-totals.test.ts, fails identically on cleanmainwhen that file is run in isolation, so it is pre-existing and order-dependent rather than introduced here. The 26 errors in both runs areapp/renderertests needingjsdomfromapp/'s own dependency tree.New coverage in
tests/providers/cline-cli.test.ts(39 tests) andtests/provider-turn-grouping.test.ts: both layouts under one provider without double counting, the~/.cline/dataoverlap invariant, the CLI root bypassing task-id dedup, a task dir named*.jsonrouting correctly, re-parse against a warm dedup set not falling back to the rollup, metered-$0-vs-absent-vs-negative cost, seconds-resolution timestamps, and the metered cost surviving the cache round trip.