Files
olp/olp-plugin
f3716a19fd fix+release(v0.4.1): D74 post-Phase-4 hotfix batch — 5 maintainer-review findings (#46)
* fix+release(v0.4.1): D74 post-Phase-4 hotfix batch — maintainer-review findings

Patch release fixing 5 issues caught by maintainer post-v0.4.0 independent
review (main / v0.4.0 / commit ee4d945). All five are real runtime bugs
the per-D-day fresh-context opus reviewers missed because they checked
spec text instead of runtime contracts (default auth.allow_anonymous:
false, real /health payload shape, real /cache/stats payload shape, real
/v0/management/dashboard-data payload shape).

Phase 4 process learning: every implementation D-day MUST include at
least one test that boots the server with default production config and
exercises the new feature end-to-end. D74 Suite 36 pins the wire-
contract shapes so a future D-day refactor can't silently re-break the
CLI / plugin / docs.

## P1-1 — olp doctor false-negative on auth-required /health

lib/doctor.mjs: buildBuiltinChecks() accepts opts.authHeaders and passes
to httpGet for both server.running + server.version probes. The
server.running check now distinguishes 401/403 ("server up, bearer token
missing or invalid — set OLP_API_KEY") from "server unreachable" so the
kind discriminator routes to a clean fix-auth path instead of fix_server
when operator just forgot to export the env var.

bin/olp.mjs cmdDoctor: threads authHeaders() through to runDoctor.

## P1-2 — olp-connect token validation + shell-quoting

bin/olp-connect: validate_olp_token() enforces ^olp_[A-Za-z0-9_-]{43}$
(per ADR 0007 § 3 token format) at THREE input sites: --key arg,
/health.anonymousKey server-advertised consumption, interactive prompt
fallback. shell_quote() POSIX-single-quote-wraps with embedded-quote
escape per:
  foo'bar  →  'foo'\''bar'
Applied to all rc-file writes + dry-run output. systemd
environment.d/olp.conf write additionally rejects embedded newlines.
Hostile or malformed keys can no longer persist as shell startup
injection.

## P2-3 — olp usage + olp cache human formatter wire-contract fix

bin/olp.mjs cmdUsage previously read body.usage_24h.requests /
body.providers / body.top_fallback_chains — none of which exist in the
real server payload (server.mjs:2027 + lib/audit-query.mjs). Users saw
"requests: ?" + missing per-provider quota + missing top-chains. Now
reads body.window_24h.request_count / body.cache_hit_24h.hit_rate /
body.quota / body.top_fallback_chains_24h.

bin/olp.mjs cmdCache previously read body.entries / body.bytes /
body.maxBytes (OCP-era field names). Real CacheStore.stats() returns
{hits, misses, size, inflightCount}. Now reads body.size /
body.inflightCount + computes hit rate from hits/(hits+misses).

## P2-4 — olp-plugin/ fmtHealth iterates providers.status

olp-plugin/index.js: previously walked Object.entries(body.providers)
which surfaced `enabled` / `available` / `status` as pseudo-providers
(chat showed "🟢 status" instead of "🟢 anthropic"). Now extracts the
real provider map from body.providers.status, renders enabled/available
counts in a header line, lists per-provider names with activeSpawns when
present. Falls back to flat body.providers.* for older OCP shape
(backwards compat).

## P3-5 — stale v0.3.0-era doc strings updated

README.md: header status line + Implementation Status § now reflect
v0.4.0 shipped + Phase 5 open. Known-limitations Phase 3 line moved out
of "pending v0.3.0" state; new Phase 4 line added with full deliverable
list.

server.mjs: startup banner no longer hardcodes "Phase 1 in progress"
(now just lists version + provider count). Banner derives state from
VERSION so future Phase boundaries don't need touch-ups here.

## Test count

696 (v0.4.0) → 704 (v0.4.1). +8 D74 regression tests in Suite 36:

- 36a: runDoctor accepts authHeaders + threads to checks
- 36b: server.running distinguishes 401 (auth) from "server down"
- 36c: olp-connect rejects malformed --key (validator fires before rc write)
- 36d: olp-connect accepts properly-formed olp_ token
- 36e: CacheStore.stats() shape pin + cmdCache source pin
- 36f: dashboard-data payload shape pin + cmdUsage source pin
- 36g: olp-plugin fmtHealth iterates providers.status not providers.*
- 36h: server.mjs banner doesn't hardcode stale phase

## Authority

- Maintainer independent review of main / v0.4.0 / commit ee4d945
  (2026-05-26 session — 5 findings P1×2 + P2×2 + P3×1)
- Iron Rule 第二律 (evidence over "should work") — runtime smoke
  against default production config now mandatory per Suite 36 pattern
- CLAUDE.md release_kit.phase_rolling_mode cross-Phase discipline
  ("hotfix to a shipped Phase N deliverable → bump patch, tag, release
  before next push")
- ADR 0007 § 3 (token format ^olp_[A-Za-z0-9_-]{43}$) — D74 P1-2
  validator authority

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix: Suite 36 paths use import.meta.dirname for CI portability (was hardcoded /Users/taodeng/olp/)

---------

Co-authored-by: dtzp555 <dtzp555@gmail.com>
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-26 10:43:12 +10:00
..

olp-plugin

OpenClaw gateway plugin that exposes a /olp slash command on Telegram and Discord, with subcommand parity to the local olp CLI (bin/olp.mjs) minus mutating operations.

Authority: ADR 0010 § Phase 4 D71-D73.

Status

Shipped at v0.4.0 (read-only subset of olp CLI).

What you can do from chat

Slash command Maps to Tier
/olp status GET /v0/management/status owner
/olp health GET /health public
/olp usage GET /v0/management/dashboard-data owner
/olp models GET /v1/models public
/olp cache GET /cache/stats owner
/olp providers local registry view public
/olp chain show [model] local chain view (empty unless wired) public
/olp doctor informational only (HTTP doctor endpoint not yet shipped)
/olp help usage text

What you can NOT do from chat (by design)

The following olp CLI subcommands are deliberately not ported to the chat surface, because Telegram + Discord are shared / persistent message streams and key material or raw audit logs should not be flowing across them:

  • olp keys keygen — key material would land in chat history
  • olp keys revoke — accidental misclick could lock out clients
  • olp restart — a misclick should not cycle the proxy
  • olp logs — audit content may carry PII

Use SSH to the host running OLP and the local olp CLI for those.

Install

The plugin is shipped inside the OLP repo at olp-plugin/. Two install paths:

Option A — OpenClaw CLI

openclaw plugins install /path/to/olp/olp-plugin/
mkdir -p ~/.openclaw/extensions/
ln -s /path/to/olp/olp-plugin/ ~/.openclaw/extensions/olp

Either path makes the plugin discoverable; restart the gateway to pick it up:

openclaw gateway restart

Configure

Edit ~/.openclaw/openclaw.json and add a config block for the olp plugin:

{
  "plugins": {
    "olp": {
      "proxyUrl": "http://127.0.0.1:4567",
      "apiKey": "olp_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
    }
  }
}
  • proxyUrl — full URL of the OLP proxy. Default http://127.0.0.1:4567 (OLP's default since v0.4.0 / D60). Overridable via OLP_PROXY_URL or OLP_PORT env if you run the gateway under launchd / systemd with custom env.

  • apiKeyowner-tier OLP API key. Required for the subcommands marked owner in the table above. Create one with:

    # On the OLP host, NOT in chat:
    npx olp-keys keygen --owner --name=openclaw-bot
    # Capture the plaintext token from the output — it is printed exactly once.
    

    Use a dedicated bot key (the --name=openclaw-bot example above) so you can npx olp-keys revoke --id=<id> later without affecting the maintainer's personal key.

Use

In Telegram or Discord, after the gateway picks up the plugin:

/olp status
/olp usage
/olp models
/olp help

Output is wrapped in a monospace code block. Long responses are truncated to fit Telegram's ~4096-char per-message limit; a ... [truncated, use SSH for full] suffix marks where the cut happened.

Authorization model

The plugin sends Authorization: Bearer <apiKey> on every request. OLP's server enforces:

  • public-tier endpoints (/health, /v1/models) accept any non-revoked key (or no key at all if auth.allow_anonymous: true).
  • owner-tier endpoints (/v0/management/*, /cache/stats) reject any non-owner key with 403.

If you see 401 unauthorized or 403 forbidden in chat:

  • Verify the configured apiKey is a non-revoked owner-tier key.
  • Verify the key was created on the same host running the OLP server (keys are stored under ~/.olp/keys/ and validated by hash on the server side).
  • Check the OLP server's /health directly with curl to confirm reachability.

Port resolution priority

  1. OLP_PROXY_URL env (full URL) — useful when the gateway runs on a different host than OLP and you proxy in via Tailscale.
  2. OLP_PORT env (port only; localhost assumed).
  3. Plugin config proxyUrl.
  4. Fallback http://127.0.0.1:4567.

Why no Telegram/Discord SDK dependency

OpenClaw provides the transport (Telegram bot + Discord bot are gateway features). This plugin only registers a slash command — it does not open its own websocket / long-poll connection. That means:

  • No new npm dependency.
  • No bot tokens stored in plugin config.
  • Plugin works for any OpenClaw-supported chat surface (currently Telegram + Discord; future surfaces inherit automatically).

Cross-references