Skip to content

feat(cline): read the Cline CLI's sessions layout in the cline provider - #891

Merged
iamtoruk merged 1 commit into
getagentseal:mainfrom
therickfactr:feat/cline-cli-sessions-provider
Aug 3, 2026
Merged

feat(cline): read the Cline CLI's sessions layout in the cline provider#891
iamtoruk merged 1 commit into
getagentseal:mainfrom
therickfactr:feat/cline-cli-sessions-provider

Conversation

@therickfactr

@therickfactr therickfactr commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

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-cli provider, now folded into cline).

Summary

  • Discovery gains the CLI's sessions/<id>/ layout as a third root of the existing cline provider — no second provider, so Cline stays one row and the CLI root stays outside the task-id dedup pass.
  • The CLI format shares nothing with the VS Code one, so its parsing lives in a new src/providers/cline-cli-parser.ts. vscode-cline-parser.ts is untouched — Roo Code, KiloCode and IBM Bob all ride on it.
  • Root resolution mirrors the CLI's own chain, each level overridable: CLINE_SESSION_DATA_DIRCLINE_DATA_DIRCLINE_DIR~/.cline. probeRoots() reports every root so doctor distinguishes "not installed" from "override pointing elsewhere".
  • The cline parse version is bumped so warm caches re-derive once.

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/data is both a task root and the parent of sessions/ 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 .json suffix, so a task directory that happens to be named weird.json still goes to the task parser. Emitted calls carry provider: 'cline'; dedup keys stay namespaced cline-cli:<sessionId>:<messageId> so they can never collide with cline:<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: false and keep their reported cost instead of being re-priced from tokens. The pass-through in parser.ts is 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.)

modelDisplayName now resolves names instead of passing ids through. The CLI records routed ids like cline-pass/glm-5.2, unreadable raw. Side effect: extension ids get their real labels too (claude-sonnet-4-5Sonnet 4.5). That is a visible change for existing users, so it has an updated test rather than a silent flip. toolDisplayName maps only the CLI's tool vocabulary; no key in that table collides with a Cline VS Code tool name (read_files vs read_file, search_codebase vs search_files, ask_question vs ask_followup_question are all distinct), so extension names pass through untouched.

Testing

  • I have tested this locally against real data (not just unit tests)
  • npm test passes
  • npm run build succeeds

For new providers only

Not a new provider any more, but the same evidence bar applies:

  • I installed the tool and generated real sessions by using it
  • npm run dev -- today shows correct costs and session counts
  • npm run dev -- models --provider cline shows correct model names and pricing
  • Terminal output attached below

Correctness against the source of truth — summing the metrics blocks straight off disk versus what CodeBurn reports:

codeburn: 142 calls, in 4,934,762, out 224,561, cost $2.12
on disk : 142 calls, in 4,934,762, out 224,561, cost $2.1168

Exact on calls and tokens; cost matches the CLI's own metered total to the cent.

codeburn doctor — one row, every root probed:

│  Cline              │        18  │    8/8+  │      18  │  OK (18 sessions)  │
    Cline tasks: ~/Library/Application Support/Code/User/globalStorage/saoudrizwan.claude-dev (missing)
    Cline tasks: ~/Library/Application Support/Code - Insiders/User/globalStorage/saoudrizwan.claude-dev (missing)
    Cline tasks: ~/Library/Application Support/VSCodium/User/globalStorage/saoudrizwan.claude-dev (missing)
    Cline tasks: ~/.cline/data (exists)
    Cline CLI sessions: ~/.cline/data/sessions (exists)

npm run dev -- models --provider cline --period month:

│ Provider   │ Model                │ Top Task             │   Input │  Output │   Total │    Cost │
│ Cline      │ Kimi K3              │ Testing (85%)        │  544.5K │   25.9K │    1.1M │  $0.705 │
│ Cline      │ qwen3.7-max          │ Testing (77%)        │  413.6K │   22.5K │  756.7K │  $0.562 │
│ Cline      │ Kimi K2.6            │ Feature Dev (51%)    │    1.0M │   32.5K │    2.0M │  $0.376 │
│ Cline      │ GLM-5.2              │ Testing (75%)        │  461.2K │   83.8K │  867.3K │  $0.246 │
│ Cline      │ minimax-m3           │ Feature Dev (84%)    │    1.7M │   24.0K │    3.4M │  $0.154 │
│ Cline      │ DeepSeek v4 Flash    │ Debugging (99%)      │  727.4K │   35.2K │    1.4M │  $0.056 │
│            │ Total                │                      │    4.9M │  224.0K │    9.5M │   $2.10 │

(The two ids still showing raw there are unrelated SHORT_NAMES gaps, 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 main and here alike (23–24 timeouts either way). Assertion failures: 3 on main, 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 clean main when that file is run in isolation, so it is pre-existing and order-dependent rather than introduced here. The 26 errors in both runs are app/renderer tests needing jsdom from app/'s own dependency tree.

New coverage in tests/providers/cline-cli.test.ts (39 tests) and tests/provider-turn-grouping.test.ts: both layouts under one provider without double counting, the ~/.cline/data overlap invariant, the CLI root bypassing task-id dedup, a task dir named *.json routing 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.

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
@ozymandiashh
ozymandiashh force-pushed the feat/cline-cli-sessions-provider branch from 56ec75d to 448d470 Compare August 3, 2026 23:45
@ozymandiashh

Copy link
Copy Markdown
Collaborator

Maintainer note: the check failure was the Co-Authored-By: Claude ... trailer, which this repo blocks in commit messages. I amended the commit in place to drop that one line (diff, authorship and the rest of the message untouched) and force-pushed, so remember to git pull --rebase before any further local work on this branch. Review of the substance follows separately.

@iamtoruk iamtoruk left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@iamtoruk
iamtoruk merged commit 6ad42fa into getagentseal:main Aug 3, 2026
4 checks passed

@ozymandiashh ozymandiashh left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

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.

3 participants