Skip to content

feat: flag Agents with a newer published version; read npm facts from the catalog - #63

Merged
yujiezhang-ops merged 2 commits into
mainfrom
fix/agent-update-dot-and-npm-source
Aug 6, 2026
Merged

yujiezhang-ops merged 2 commits into
mainfrom
fix/agent-update-dot-and-npm-source

Conversation

@yujiezhang-ops

Copy link
Copy Markdown
Collaborator

Closes #61.

Adds the update dot, and fixes #61 along the way — the second is what makes the first correct rather than a third hardcoded list.

The dot

Nothing could tell an Agent was out of date. lockedVersion, which versionNote compares against, is hardcoded to nil in status.goagents.lock.json deliberately records no versions — so isBehind never returned true and the "behind" branch of versionNote was unreachable. Knowing meant asking the registry, which nothing did.

install.LatestVersion asks, and says nothing when it cannot. Every failure is silent by design: this decorates a button, so an offline machine, a rate-limited registry, or a captive portal leaves the row exactly as it was rather than turning a status poll into an error — or claiming "up to date" on no evidence. nil means say nothing, not current.

It streams instead of decoding into a struct, and that is not premature. opencode-ai's abbreviated document is 13 MB because it carries an entry per published version, while dist-tags sits in the first 24 bytes. Reading all of it forced a choice between a size cap that truncated the JSON (unexpected EOF, with the answer long since read) and no cap at all, letting a registry decide the memory bill. I hit exactly this: opencode-ai returned "" under a 4 MB cap while openclaw at 0.6 MB worked fine.

Costs are bounded: answers cached 30 minutes, at most 3 concurrent requests, one deadline for the batch. The UI polls status — without the cache that is one request per Agent per poll. Only installed npm Agents are asked about; Aider comes from PyPI through uv, and there is nothing to update on a machine without the Agent.

Where npm facts come from (#61)

The npm-managed set was a literal in AgentManageRow, beside a second literal mapping each Agent to its package name. Both had drifted: OpenClaw shipped as an npm Agent in #53 and was in neither, so it silently lost its update button and its package name in the details — nothing tied those lists to agents.lock.json, so no test or build could catch it. The catalog now carries packageManager and packageName from the same manifest the installer reads.

The update button also ignored installed, though the launch button in the same component did not. npm update -g on a never-installed package exits 0 and does nothing, so the task centre reported "更新完成" for an Agent that was still missing — worse than an error, because it looks like it worked. The button is now hidden, and UpdateAgent refuses, since the CLI and a restored task card do not go through the UI.

Verification

Against the real npm registry, on a machine with all five installed:

Agent installed latest dot
codex 0.146.0 0.146.1
openclaw 2026.6.5 2026.7.1-2
claude-code 2.1.223 2.1.223
kilo-cli 7.4.20 7.4.20
opencode 1.18.14 1.18.14
aider 0.86.2 never asked (uv)

OpenClaw showing a dot is the #61 bug fixed and the feature working in one row.

  • go test ./... and go vet ./... pass; pnpm run test 26 files / 197 tests pass; pnpm run build and tsc --noEmit pass
  • New Go tests cover the 13 MB streaming case, out-of-order keys, every silent-failure path, https-only, traversal, concurrency cap, and the cache
  • Frontend tests cover the dot's presence and absence, the hidden button when not installed, and the package name coming from the catalog
  • Built and ran the desktop binary; no errors in the log

tsc caught three test fixtures missing the new field that vitest had not — those are the one-line additions to the page tests.

Note

lockedVersion is left alone. It means "the version the lock pins", which is a different thing from "the newest published version", and conflating them would be wrong even though it is currently always nil.

🤖 Generated with Claude Code

…facts from the catalog

Two things, because the second is what made the first possible to write
correctly.

## The dot

There was no way to know an Agent was out of date. lockedVersion, which
versionNote compares against, is hardcoded to nil in status.go -- agents.lock.json
deliberately records no versions -- so isBehind never returned true and the
"behind" branch of versionNote was unreachable. Knowing required asking the
registry, which nothing did.

install.LatestVersion asks, and says nothing when it cannot. Every failure is
silent on purpose: this decorates a button, so an offline machine, a rate-limited
registry, or a captive portal leaves the row exactly as it was rather than
turning a status poll into an error or claiming "up to date" it cannot support.
nil means "say nothing", not "current".

It streams rather than decoding into a struct, and that is not premature. The
abbreviated document for opencode-ai is 13 MB because it carries an entry per
published version, while dist-tags sits in the first 24 bytes. Reading all of it
forced a choice between a size cap that truncated the JSON -- unexpected EOF with
the answer long since read -- and no cap, letting a registry decide how much
memory this spends. Streaming to the key needs neither.

Answers are cached for 30 minutes and at most three requests run at once. The UI
polls status; without the cache that is a request per Agent per poll, which is
both wasteful and the fastest way to be rate-limited. Only installed npm Agents
are asked about: Aider comes from PyPI through uv, and there is nothing to update
on a machine that does not have the Agent.

## Where npm facts come from (#61)

The set of npm-managed Agents was a literal in AgentManageRow, beside a second
literal mapping each to its package name. Both had already drifted: OpenClaw
shipped as an npm Agent in #53 and was in neither, so it silently lost its update
button and its package name in the details -- with no test or build to catch it,
because nothing tied the two lists to agents.lock.json. The catalog carries
packageManager and packageName now, from the same manifest the installer reads.

The update button also ignored whether the Agent was installed, though the launch
button in the same component did not. `npm update -g` on a package that was never
installed exits 0 and does nothing, so the task centre reported "update complete"
for an Agent that was still missing -- worse than an error, because it looks like
it worked. The button is now hidden, and UpdateAgent refuses, since the CLI and a
restored task card do not go through the UI.

Verified against the real registry: codex 0.146.0 -> 0.146.1 and openclaw
2026.6.5 -> 2026.7.1-2 show a dot, claude-code, kilo-cli and opencode are current
and show none, and aider is never asked.
golang.org/x/mod arrived with the OTA updater in #62 and its manifest entry was
written by hand, without the `modification` key every other dependency carries.
The generator emits it unconditionally -- an empty value is how the bundle states
"redistributed unmodified" -- so the tracked file and a fresh generation differed
by that one line and the Release compliance job failed.

This is main's failure, not this branch's: `--check` fails the same way on a
clean checkout of main at bd143bb, and it has been red since #62 merged. The fix
is running the generator, which is what the job's own error message asks for.
@yujiezhang-ops
yujiezhang-ops merged commit 43d70e3 into main Aug 6, 2026
4 checks passed
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.

[P2] 「更新」按钮用前端硬编码的 npm 清单判断,已漏掉 OpenClaw,且未安装也照样显示

1 participant