Skip to content

feat(claude-code-plugin): OpenViking statusline (opt-in)#1890

Merged
ZaynJarvis merged 16 commits intovolcengine:mainfrom
t0saki:feat/cc-statusline
May 7, 2026
Merged

feat(claude-code-plugin): OpenViking statusline (opt-in)#1890
ZaynJarvis merged 16 commits intovolcengine:mainfrom
t0saki:feat/cc-statusline

Conversation

@t0saki
Copy link
Copy Markdown
Contributor

@t0saki t0saki commented May 7, 2026

Description

Adds an optional one-line OpenViking statusline rendered below the Claude Code input box. Surfaces live signals from auto-recall / auto-capture / session-start hooks plus a 5 s shared cache of GET /health, so users get visibility into what OV is doing without leaving the TUI. Opt-in at install time — existing user statuslines aren't clobbered.

Related Issue

N/A.

Type of Change

  • New feature (non-breaking change that adds functionality)
  • Documentation update

Changes Made

  • New scripts/statusline.mjs entry composing: health (OV ✓ / ⚠ slow / ✗ offline / ⚡ bypass), recall (↩ N mem (score) · Yms), capture (✎ X/20k · M arch, ✎ committed, ✗ N dropped), session events (🔗 resumed / 🔗 compact, 1 min TTL), daily archive count (+N today). 80-char hard cap with ANSI-aware truncation; NO_COLOR / OPENVIKING_STATUSLINE_NO_COLOR / TERM=dumb degrade.
  • auto-recall.mjs / auto-capture.mjs / session-start.mjs write small JSON snapshots to ~/.openviking/state/ via atomic temp+rename in new lib/state.mjs. State files: last-recall.json, last-capture.json, last-session-event.json, daily-stats.json.
  • New lib/server-probe.mjs: file-cached 5 s shared probe of /health with a 1 s hard timeout (originally 250 ms — loosened after spurious offline reports on remote SaaS deployments).
  • setup-helper/install.sh adds register_statusline() — opt-in [y/N] prompt; detects existing .statusLine and offers replace / skip; idempotent on re-runs; backups ~/.claude/settings.json.bak.$ts before merge.
  • New examples/claude-code-memory-plugin/docs/STATUSLINE.md: segment glossary (every state combo + when each is missing) and personalization recipes (drop a segment, recolor, compose with another statusline, custom segments, reset state). Anchored on $REPO / $PLUGIN / $STATE so an agent resolves all paths from the install location alone.
  • Install script prints a copy-pasteable seed prompt at the end inviting users to open Claude Code and ask it to walk them through their statusline.
  • Integration docs (docs/{en,zh}/agent-integrations/02-claude-code.md) gain a refreshed examples block (with ⚠ slow, 🔗 resumed, +N today) and an absolute GitHub link to STATUSLINE.md (since examples/... is outside the VitePress docsRoot).
  • .claude-plugin/plugin.json version bump 0.2.00.3.0.

Testing

  • I have added tests that prove my fix is effective or that my feature works
  • New and existing unit tests pass locally with my changes
  • I have tested this on the following platforms:
    • Linux
    • macOS
    • Windows

Manual end-to-end on macOS: opted in via installer, exercised OV ✓ / ⚠ slow (unroutable host) / ✗ offline (refused port) / ⚡ bypass; verified each segment appears and clears against expected hook activity; verified 80-char truncation and ANSI degrade. The statusline is a thin composition over hook-written state and a probe; no unit tests added (smoke-tested via node scripts/statusline.mjs <<<'{...}').

Checklist

  • My code follows the project's coding style
  • I have performed a self-review of my code
  • I have commented my code, particularly in hard-to-understand areas (probe timeout reasoning, sawtooth pending_tokens, cc_session_id filtering)
  • I have made corresponding changes to the documentation
  • My changes generate no new warnings
  • Any dependent changes have been merged and published

Screenshots (if applicable)

img_v3_0211f_def29bba-d488-4e19-9080-c63a256cebhu

Additional Notes

  • Opt-in: existing user statuslines are detected; installer prompts replace / skip / manual-compose.
  • Kill switches: OPENVIKING_STATUSLINE=off (silence without unregistering) or jq 'del(.statusLine)' ~/.claude/settings.json (remove).
  • OV ⚠ slow (yellow) is the dedicated state for probe timeout — distinct from OV ✗ offline (red) so a slow remote isn't alarmist.

t0saki added 13 commits May 7, 2026 15:04
A one-line OV status renders under the CC input box: server health,
last-turn recall stats, pending capture, and queue alerts. Network
calls share a 5 s file cache and have a 250 ms hard timeout so the
statusline never blocks render.

- scripts/statusline.mjs: main entry, ANSI degrade, 80-char cap
- scripts/lib/state.mjs: atomic JSON state writer + TTL reader
- scripts/lib/server-probe.mjs: cached /health (+ /observer/queue)
- auto-recall / auto-capture: write last-recall.json / last-capture.json
- setup-helper/install.sh: opt-in prompt; replace-or-skip for existing
  user statusline; backup + restore-instructions
- bump plugin version 0.2.0 -> 0.3.0
Queue probe was sharing the /health 250 ms budget; on remote servers
where /health used 200ms+, the queue probe got ~50ms or was skipped
entirely, so queue_healthy flapped between false (when /health was
fast) and null (when /health was slow). Result: ⚠ queue badge appeared
intermittently even when the queue was consistently unhealthy.

Worst-case statusline latency goes from 250ms to 500ms; typical case
is unchanged (~150ms) since both endpoints respond in tens of ms when
the server is healthy.
…e count

- 250ms was too aggressive for remote OV servers; ordinary network jitter
  (200-400ms /health) was producing spurious "OV ✗ offline" flicker.
  Bumped to 1s per endpoint. Worst case render is now ~2s but the 5s
  cache amortises this to once per 5s window per session.

- pending_tokens is a sawtooth: it climbs to commit_threshold then snaps
  to 0 on commit. Showing only "X/20k tok" of a long conversation read
  as "we only captured X tokens", which hid the work already archived.
  Now the statusline also shows "N arch" — the running commit_count
  pulled from the OV session metadata. So a long session now shows
  e.g. "✎ 573/20k tok · 2 arch" instead of just "✎ 573/20k tok".
QueueObserver.is_healthy() is derived from QueueManager.has_errors(),
which is `any(q._error_count > 0 for q in queues)`. _error_count is a
lifetime cumulative counter that never resets, so any server with a
single transient embedding failure ever flips is_healthy to false
forever — even when the next 1000 jobs all succeed.

Real example from a production server: 41 jobs processed, 2 historic
errors (95%+ success rate), is_healthy returns false. The badge then
appears constantly for users whose OV experience is fine.

Removing the badge and the second network round-trip. Connectivity
(OV ✓), recall activity (↩ N mem), and capture progress (✎ N/20k arch)
already cover whether OV is functioning end-to-end.
- ↩ N mem (0.92): max recall score appended in parens. Quality hint
  without an extra segment. auto-recall.mjs now writes top_score in
  last-recall.json.

- ✗ N dropped: turns that auto-capture failed to push this batch. Not
  sticky — auto-capture overwrites last-capture.json each Stop hook,
  so transient failures clear themselves on next success. Sustained
  failures stay visible (which is when the user needs to know).

- 🔗 resumed / 🔗 compact: session-start.mjs writes a 1-min TTL
  event when CC source is resume or compact. Lets the user see that
  OV did re-hydrate context across restarts instead of having to
  guess.

- +N today: cross-session daily commit_count. auto-capture maintains
  daily-stats.json (resets on date rollover). Hidden when 0 to keep
  fresh-day mornings unobtrusive. Distinct from per-session "M arch"
  which only counts the current CC session.

Truncation order verified: server → recall → capture → dropped (alert)
→ resumed (info) → today (info). 80-char cap drops the lowest-priority
tail when the line gets crowded.
Recall side: `tokens_used` is a chars/4 heuristic (estimateTokens in
auto-recall.mjs), not real tokens. For CJK-heavy text the heuristic
underestimates by 2-4x, so labelling it "tok" is misleading.

Capture side: `pending_tokens` comes from the server, but the server's
own counter is also approximate. Mixing the two under the same label
invites the wrong mental model.

Just drop the unit. The magnitude is meaningful on its own (1.2k =
medium injection, 573/20k = 3% of next archive). Configuration field
names (recallTokenBudget, commitTokenThreshold) keep "Token" so we
don't churn user-facing config.
…eading

The "1.2k" between mem count and latency was estimateTokens(text) =
ceil(text.length / 4) on the assembled injection block. For CJK-heavy
content the heuristic underestimates by 2-4x, which is enough that
showing the number does more harm than presenting count + score +
latency alone.

Capture side keeps "573/20k" because the server reports pending_tokens
itself (more accurate, and the ratio against threshold is meaningful
even if the absolute count is approximate).
…ompact

Statusline expected `🔗 resumed/compact` to reflect that the event happened,
but session-start.mjs only wrote the marker when `formatArchiveContext` had
something to inject. Fresh sessions with no prior archive saw a `/compact`
silently — statusline showed nothing, leaving the user wondering whether the
hook fired at all.

Move the writeJsonState call ahead of the no-archive early return and tag
the payload with `had_context: false` for the empty case. The badge now
fires on every resume/compact event with a 1-minute TTL.

`✎` capture pending is unaffected — that segment is gated on
`cc_session_id === sessionId` and after `/branch` there's no Stop hook for
the new session yet, which is correct (stale capture from a different
session would be misleading).
Statusline has more knobs than env vars expose — segment ordering, colors,
composing with another statusline, custom segments, state file shapes — and
the integration doc is the wrong venue for that level of detail. Add a
recipe-style guide aimed at an AI assistant reading it end-to-end, so users
can ask Claude Code "personalize my statusline" instead of spelunking source.

- examples/claude-code-memory-plugin/docs/STATUSLINE.md: recipes (drop a
  segment, recolor, compose, reset state, add a custom segment) + state
  file schemas + pointers to the canonical files. Defers env-var reference
  back to docs/en/agent-integrations/02-claude-code.md.
- install.sh: print a copy-pasteable seed prompt at the end of install. Not
  intrusive — no auto-launch, just a tip the user can ignore.
- docs/{en,zh}/agent-integrations/02-claude-code.md: cross-link the new doc
  from the Statusline section.
Recipes referenced \`scripts/statusline.mjs\` etc. with no anchor, so an
agent reading the doc had no way to resolve them — `~/.openviking/openviking-repo/examples/claude-code-memory-plugin/scripts/...`
is far enough off the beaten path that "go look in scripts/" doesn't land.

Define \`\$REPO\` / \`\$PLUGIN\` / \`\$STATE\` once at the top with how to
verify each (jq on settings.json, find as fallback), then propagate the
prefixes through every recipe. The install seed prompt already passes the
absolute path of STATUSLINE.md, so the chain is now self-contained.
…pose install tip

Three small refinements after seeing the statusline in the wild:

- statusline.mjs: split the unhealthy branch — `OV ⚠ slow` (yellow) when
  the probe times out, `OV ✗ offline` (red) when it errors. Slow ≠ dead;
  red was alarmist for transient lag (e.g. remote SaaS GC pauses).

- examples/claude-code-memory-plugin/docs/STATUSLINE.md: add "What each
  segment means" — a full glossary covering every state combination
  (✓/⚠/✗/⚡, ↩, ✎ in its three forms, dropped, 🔗 resumed/compact, +N
  today), plus a "missing when?" troubleshooting list. The integration
  docs only had four example lines, two of which were stale; the canonical
  reference now lives next to the code.

- docs/{en,zh}/agent-integrations/02-claude-code.md: refresh the example
  block (drop stale `1.2k tok` / `12k/20k tok`, add ⚠ slow + 🔗 resumed
  + +N today rows), and broaden the cross-link to advertise both
  explanation and personalization.

- install.sh: rewrite the seed prompt as "walk me through what each
  segment means, then ask if I want to personalize" — covers the more
  common "what does this badge mean?" path before customization.
VitePress only ships docs under \`docs/\`, but STATUSLINE.md lives in
\`examples/claude-code-memory-plugin/docs/\` (next to the plugin code, where
it logically belongs). Relative \`../../examples/...\` resolved on GitHub
but 404'd on the published docs site.

Use an absolute https://github.com/volcengine/OpenViking/blob/main/...
URL — works in both renders, and a parenthetical note tells readers why.
It was meta — readers don't need to know why the link's absolute. Just click.
@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 7, 2026

PR Reviewer Guide 🔍

Here are some key observations to aid the review process:

⏱️ Estimated effort to review: 3 🔵🔵🔵⚪⚪
🏅 Score: 85
🧪 No relevant tests
🔒 No security concerns identified
✅ No TODO sections
🔀 Multiple PR themes

Sub-PR theme: Add statusline core functionality

Relevant files:

  • examples/claude-code-memory-plugin/scripts/lib/state.mjs
  • examples/claude-code-memory-plugin/scripts/lib/server-probe.mjs
  • examples/claude-code-memory-plugin/scripts/auto-recall.mjs
  • examples/claude-code-memory-plugin/scripts/auto-capture.mjs
  • examples/claude-code-memory-plugin/scripts/session-start.mjs
  • examples/claude-code-memory-plugin/scripts/statusline.mjs

Sub-PR theme: Add statusline installer integration

Relevant files:

  • examples/claude-code-memory-plugin/setup-helper/install.sh

Sub-PR theme: Add statusline documentation and version bump

Relevant files:

  • docs/en/agent-integrations/02-claude-code.md
  • docs/zh/agent-integrations/02-claude-code.md
  • examples/claude-code-memory-plugin/README.md
  • examples/claude-code-memory-plugin/README_CN.md
  • examples/claude-code-memory-plugin/docs/STATUSLINE.md
  • examples/claude-code-memory-plugin/.claude-plugin/plugin.json

⚡ Recommended focus areas for review

Non-atomic cache write

writeCache uses writeFileSync directly on CACHE_FILE without atomic temp+rename, risking corrupted cache under concurrent statusline invocations.

function writeCache(payload) {
  try {
    writeFileSync(CACHE_FILE, JSON.stringify(payload));
  } catch { /* best effort */ }
}
State schema inconsistency

last-recall.json uses "session_id" instead of "cc_session_id" as documented in STATUSLINE.md, inconsistent with last-capture.json and last-session-event.json.

const writeRecallState = (extra) => writeJsonState("last-recall.json", {
  server_url: cfg.baseUrl,
  latency_ms: Date.now() - t0,
  ...extra,
});
Documentation consistency

English and Chinese docs are updated for the statusline, but Japanese docs (docs/ja/agent-integrations/02-claude-code.md) are missing the same changes.

## Statusline 状态行

插件会在 Claude Code 输入框下方渲染一行 OpenViking 状态。安装脚本把它注册到 `~/.claude/settings.json`(CC 插件 manifest 不支持 `statusLine`,必须走用户 settings)。

```text
OV ✓ │ ↩ 6 mem (0.92) · 50ms             本轮注入 6 条记忆,最高分 0.92
OV ⚠ slow                                 探针超过 1s 预算(服务器可能在抽风)
OV ✗ offline                              服务器不可达
OV ⚡ bypass                               命中 OPENVIKING_BYPASS_SESSION*
OV ✓ │ ✎ 573/20k · 2 arch                 待提交进度 + 本 session 已归档 2 次
OV ✓ │ 🔗 resumed │ +3 today              session 已恢复上下文;今日累计归档 3 次

hook 脚本把每轮的小快照写到 ~/.openviking/state/;statusline 脚本读快照,再加 5 秒共享缓存的 GET /health。探针 1s 硬超时,多 session 共享缓存避免风暴。

OPENVIKING_STATUSLINE=off 可静默不删;jq 'del(.statusLine)' ~/.claude/settings.json 彻底移除。已有自定义 statusline 时安装会询问替换 / 跳过 / 手动 compose。

完整的段位说明(每个 segment 何时出现、为什么有时不显示)以及个性化 recipe(隐藏段位、改色、与已有 statusline 组合、自定义段位),参见 examples/claude-code-memory-plugin/docs/STATUSLINE.md。这份文档是写给 AI assistant 读的——最方便的方式是打开 Claude Code 把链接喂给它,让它读完文档既能解释你看到的内容,又能按你的偏好调整。


</details>

</td></tr>
</table>

A docs/ folder with one file is awkward when README.md and README_CN.md
already sit at the plugin root. Moves STATUSLINE.md alongside them and
fixes up:

- Stale opening line that pointed at the integration doc for the segment
  glossary — that glossary now lives in STATUSLINE.md itself, so the
  cross-reference is just for env vars.
- Drop the "(path notation defined just below)" parenthetical (meta).
- Update the install seed prompt and the en/zh integration cross-links to
  the new path.
@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 7, 2026

PR Code Suggestions ✨

No code suggestions found for the PR.

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds an opt-in Claude Code statusline for the OpenViking memory plugin, exposing server health plus recent recall/capture/session signals by composing local hook-written state with a short-lived /health probe cache.

Changes:

  • Introduces scripts/statusline.mjs to render a one-line, ANSI-colored (optional) statusline with truncation and fail-soft behavior.
  • Adds shared state + probe utilities (lib/state.mjs, lib/server-probe.mjs) and updates hook scripts to write JSON snapshots used by the statusline.
  • Updates the installer to optionally register .statusLine in ~/.claude/settings.json, and refreshes docs/readmes to describe the new feature.

Reviewed changes

Copilot reviewed 13 out of 13 changed files in this pull request and generated 11 comments.

Show a summary per file
File Description
examples/claude-code-memory-plugin/setup-helper/install.sh Adds opt-in statusline registration and a post-install “seed prompt”.
examples/claude-code-memory-plugin/scripts/statusline.mjs New statusline renderer reading hook state + cached health probe.
examples/claude-code-memory-plugin/scripts/session-start.mjs Writes a short-lived session-event marker for the statusline.
examples/claude-code-memory-plugin/scripts/lib/state.mjs Adds shared atomic-ish JSON state read/write helpers.
examples/claude-code-memory-plugin/scripts/lib/server-probe.mjs Adds file-cached /health probe with timeout.
examples/claude-code-memory-plugin/scripts/auto-recall.mjs Writes per-turn recall snapshot for statusline consumption.
examples/claude-code-memory-plugin/scripts/auto-capture.mjs Writes per-turn capture snapshot + daily archive counter.
examples/claude-code-memory-plugin/README.md Documents the new statusline feature.
examples/claude-code-memory-plugin/README_CN.md Chinese documentation for the new statusline feature.
examples/claude-code-memory-plugin/docs/STATUSLINE.md New personalization + segment glossary doc for assistants/users.
examples/claude-code-memory-plugin/.claude-plugin/plugin.json Bumps plugin version to 0.3.0.
docs/en/agent-integrations/02-claude-code.md Adds statusline documentation + link to STATUSLINE.md.
docs/zh/agent-integrations/02-claude-code.md Adds Chinese statusline documentation + link to STATUSLINE.md.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread examples/claude-code-memory-plugin/setup-helper/install.sh Outdated
Comment thread examples/claude-code-memory-plugin/setup-helper/install.sh
Comment thread examples/claude-code-memory-plugin/setup-helper/install.sh Outdated
Comment thread examples/claude-code-memory-plugin/scripts/lib/server-probe.mjs Outdated
Comment thread examples/claude-code-memory-plugin/scripts/lib/state.mjs Outdated
Comment thread examples/claude-code-memory-plugin/README.md Outdated
Comment thread examples/claude-code-memory-plugin/README_CN.md Outdated
Comment thread examples/claude-code-memory-plugin/README_CN.md Outdated
Comment thread examples/claude-code-memory-plugin/scripts/auto-recall.mjs
Comment thread examples/claude-code-memory-plugin/scripts/auto-capture.mjs
t0saki added 2 commits May 7, 2026 16:14
Code:
- state.mjs: derive STATE_DIR from \$OPENVIKING_HOME (with ~ expansion) so
  the override the docs already advertised actually works. Default
  unchanged. Was hard-coded to homedir().
- auto-recall.mjs: rename \`session_id\` → \`cc_session_id\` in last-recall.json
  to match last-capture.json / last-session-event.json. STATUSLINE.md
  schema already used \`cc_session_id\`. No reader filtered on the recall
  field, so this is a schema-cleanup, not a behavior change.
- install.sh: quote the plugin path inside the JSON \`command\` value, so
  CC's /bin/sh -c invocation tolerates spaces / metacharacters in
  \$REPO_DIR (custom OPENVIKING_REPO_DIR locations).
- install.sh: mktemp inside ~/.claude/ instead of \$TMPDIR, so the final
  rename is within one filesystem (atomic). Was crossing tmpfs/$HOME on
  Linux, where \`mv\` falls back to copy+unlink and isn't crash-safe.

Comments / docs (drift from earlier "drop tok / 250→1000ms" passes):
- server-probe.mjs: header comment said "Hard 250 ms" while the constant
  is 1000. Replaced with a forward-reference to the constant block which
  already explains the choice.
- README.md / README_CN.md: refresh the example block (drop \`1.2k tok\` /
  \`12k/20k tok\`, add \`⚠ slow\` / \`🔗 resumed\` / \`+N today\` rows), correct
  the hard-timeout sentence (250 ms → 1 s), cross-link STATUSLINE.md.
- install.sh: the \`info\` sample at registration time was also stale.
Statusline is additive and opt-in — no API breaks, no behavior change for
existing installs that skip the prompt. A patch bump fits better than a
minor.
Copy link
Copy Markdown
Collaborator

@ZaynJarvis ZaynJarvis left a comment

Choose a reason for hiding this comment

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

lgtm

@ZaynJarvis ZaynJarvis merged commit 268147d into volcengine:main May 7, 2026
8 checks passed
@github-project-automation github-project-automation Bot moved this from Backlog to Done in OpenViking project May 7, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

3 participants