Personal edition: sync from internal + decouple infra + harden distribution endpoints#323
Merged
Conversation
First MR on the internal GitLab mirror. Updates CLAUDE.md to reflect the new workflow (GitLab REST API instead of gh pr), adds Repo / Remotes docs, and ships an internal .gitlab-ci.yml + .gitleaks.toml so the rest of the project lifecycle stays on internal infra. See MR !1 for details.
… retarget to metabot-core (!2)
feat(core-integration): Phase 4 — delete embedded memory + skill-hub, retarget to metabot-core See merge request xvirobotics/metabot!2
feat(cli): unified `metabot` dispatcher (bridge + metabot-core feature CLI) See merge request xvirobotics/metabot!4
feat(install): metabot-core era — drop embedded MetaMemory + dead skills See merge request xvirobotics/metabot!5
fix(install): symlink-safe cp guard for lark-cli mirror loops See merge request xvirobotics/metabot!6
feat(agent-bus): drop per-bot talkSecret; visibility is the permission See merge request xvirobotics/metabot!7
chore: monorepo merge — subtree + workspaces + boundary triple-line See merge request xvirobotics/metabot!8
docs: monorepo sweep — CLAUDE.md, READMEs, architecture, CHANGELOG See merge request xvirobotics/metabot!9
refactor(install+cli): in-tree core CLI resolution + selective workspace install See merge request xvirobotics/metabot!10
fix(memory-cli): default create/mkdir into caller namespace, add --path See merge request xvirobotics/metabot!11
refactor(cli): fold mb into metabot — single CLI binary See merge request xvirobotics/metabot!12
chore(cli): remove dead bin/mm + bot-skills surface See merge request xvirobotics/metabot!13
fix(web-ui): memory tab 404s on folder navigation See merge request xvirobotics/metabot!14
MR !14 fixed multi-segment path lookups but missed single-segment top-level folders (/shared, /t5t). On the browser cookie path, oauth2-proxy/Caddy collapses the `//` route-prefix boundary and strips the path's leading slash; decodeMemoryIdOrPath only re-added it when the slice contained an interior slash, so a bare `shared` was treated as a UUID id and findFolderById missed → 404. Disambiguate by UUID shape instead: anything not UUID-shaped is a path that lost its leading slash. Preserves genuine id lookups. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
fix(web-ui): memory tab 404s on top-level folder navigation See merge request xvirobotics/metabot!15
…sure, card dedup - message-bridge: TTL sweep + executor-removed cleanup for chatId-keyed maps (recentQuestionCard, exitPlanCardsShown); destroy() clears all timers/buffers; new async destroyAsync() awaits executor shutdown - executor-registry: crashed executors are parked and resurrected by resuming the same Claude session (500ms..30s backoff, 3 attempts); intentional release/LRU eviction never triggers respawn - index: graceful shutdown awaits bridge teardown (15s cap) before exit - peer-manager: forwardTask() only targets known/verified peers; optional METABOT_ALLOWED_PEER_CIDRS constraint; rejected forwards logged - http-server: /api/health returns minimal liveness only; rich payload moved to authenticated /api/status - feishu: dedup card-builder/card-builder-v2 shared constants+helpers into card-builder-utils Tests: 453 passed (46 files), incl. new crash-resurrection (7) and SSRF validation (4) tests. tsc -p tsconfig.bridge.json clean. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The hardened /api/health handler returns only minimal liveness info by design, but the auth middleware ran before it and rejected unauthenticated probes with 401. Verified live on a trunks-only test bridge: /api/health 200 without auth, /api/status 401 without / 200 with the API secret. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- New request-rate-limiter: per-IP sliding window (300 req/min global, 10 failed auths -> 60s lockout), LRU-capped + swept bookkeeping, METABOT_RATE_LIMIT_MAX / _AUTH_FAILS / _DISABLED env knobs, GET /api/health exempt, 429 with Retry-After - API secret and WS token comparisons now use sha256 + crypto.timingSafeEqual (no timing/length side channels) - /api/status adds memory rss/heapUsed, executor pool total/active, rate-limiter tracked IPs Live-tested on trunks test bridge: lockout after 10 fails, health exempt during lockout, 60s self-heal, /api/talk E2E green. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
99 new tests: /status /model /memory /sync command handling (43), agent-team-store CRUD/lifecycle/cascade (37), one-time task scheduling/persistence/retry (19). Scheduler tests isolate SESSION_STORE_DIR to a temp dir so they never race a live bridge writing ~/.metabot/scheduled-tasks.json. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
/api/health is now documented as unauthenticated minimal liveness; rich diagnostics documented under authenticated /api/status; new peer-forwarding CIDR allowlist env var added to .env.example and the env reference (en/zh). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
require() inside vi.mock factory → async factory with dynamic import; let → const for the never-reassigned removed arrays. Introduced by the review-batch-1 merge (pipeline 342 on main failed). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
fix: lint errors breaking main CI (executor-registry-crash.test.ts) See merge request xvirobotics/metabot!96
VPN clients with smart split-tunneling (飞连) can capture *.feishu.cn routes into a tunnel that is down while still claiming connected. Live ws sockets held by an old process keep working, but the next metabot restart cannot reconnect — so the failure surfaces right after an upgrade and looks like the upgrade broke message delivery. METABOT_LOCAL_ADDRESS=<ip> pins the source address of every Feishu socket so the OS source-based-routes them out the interface owning that IP, bypassing the tunnel: - REST: set httpsAgent on the lark SDK's shared defaultHttpInstance, which every lark.Client here uses. Mutating it (instead of passing a custom httpInstance) keeps the SDK's interceptors intact — a custom axios instance must re-implement the response-unwrap interceptor or the SDK's internal tenant-token fetch silently breaks. - wss: pass the same agent to lark.WSClient, whose agent option goes straight to the WebSocket constructor. No undici global dispatcher: nothing here calls Feishu via native fetch, and a global dispatcher would re-route peer/metabot-core traffic out of scope. Unset env → nothing constructed, zero behavior change. Reported-by: shizhanxu Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
feat: METABOT_LOCAL_ADDRESS — bind Feishu sockets to a source IP (bypass 飞连 smart routing) See merge request xvirobotics/metabot!95
…, durationMs, exec, atomic token) Six confirmed defects from the 2026-06-15 repo review whose original fix session was lost before commit: 1. T5T cross-project authz bypass: appendWipItem/appendTopFive adopted prev.project from a guessable id; now assert prev.project === input.project. 2. doc-sync update branch lacked try/catch; one failed update aborted the whole sync. Wrap and record the error per-document. 3. rate-limiter invoked fn()/pendingFn() un-awaited -> unhandled rejection. 4. message-bridge returned lastState.durationMs (never set) -> 'Duration: NaN s'; use the locally computed durationMs in both return paths. 5. file-routes used execSync string interpolation for soffice -> execFileSync. 6. wechat-client token write was non-atomic -> tmp file + rename. Typecheck clean (root + packages/server); 349 tests pass. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
fix: review batch-2 hardening (t5t authz, sync, rate-limiter, durationMs, exec, atomic token) See merge request xvirobotics/metabot!97
…s) + harden distribution endpoints
Prepares this public GitHub tree as a self-hostable personal edition,
synced from internal main. Changes:
Defaults (were hardcoded to internal hosts/paths — broke external installs):
- METABOT_CORE_URL defaults -> http://localhost:9200 across cli-core, cli,
server, memory-client, bin/metabot, web-ui, and CLI help text.
- chat-store + server data dir defaults /vepfs/users/floodsung/... ->
~/.metabot-core/{chat-files,data}.
- installers (install.sh/.ps1, bootstrap.sh, install-cli.sh) clone from GitHub,
default to localhost.
Security:
- /cli/* and /install/* distribution endpoints were anonymous on the assumption
of a corporate VPN front door. Personal edition has no such fallback, so they
are now token-gated by DEFAULT; set METABOT_PUBLIC_DISTRIBUTION=1 to opt back
into anonymous serving. Tests updated (default-deny + opt-in + token paths).
Isolation:
- Removed internal-only docs (docs/internal/**, docs/metabot-core/**),
Feilian/oauth2-proxy/systemd deploy units, internal .claude/plans, and the
internal ROADMAP.
- Generalized deploy/install.sh + metabot-core.service to a generic self-host
flow; reframed all Feilian/SSO references as an OPTIONAL proxy.
- Rewrote public docs (README, getting-started, configuration, SKILL) for a
single local API token, no SSO/VPN.
Verified: root + packages/server + web-ui typecheck clean; 594 bridge + 355
server tests pass.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… roadmap TODOs - Add '自托管 & 鉴权 / Self-Hosted & Auth' section (zh+en): local-first, single local API token, no SSO/VPN, distribution endpoints token-gated by default with METABOT_PUBLIC_DISTRIBUTION opt-in. - Remove stale roadmap items: async agent protocol (already ships as the agent bus / Agent-to-Agent) and multi-tenant mode (counter to the personal edition's positioning). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…n decoupling From multi-agent review of personal-edition-preview: SECURITY (high): the /cli/* + /install/* token gate used exact-path matching, so slash variants (/cli//install.sh, /cli/install.sh/) skipped the gate but were still served by the UI-host static layer (path.join collapses the slashes). Fix: (1) normalize slashes before the gate match so variants are gated, and (2) defense-in-depth — tryServeStatic now refuses to serve the cli/ and install/ subtrees entirely. Added regression tests (default host + UI host, both endpoints). DOCS/ISOLATION: - install.sh + .env.example onboarding no longer tells personal-edition users to 'sign in via SSO' — token is auto-generated at ~/.metabot-core/token. - Removed internal host leak teamclaude.xvirobotics.com from executor.ts comment. - server README no longer references deploy files deleted in this branch (caddy/snippet.caddyfile, oauth2-proxy/*, cert-renew); TLS/SSO framed as BYO. - Documented METABOT_PUBLIC_DISTRIBUTION in env-vars doc, server README, .env.example. - Dropped stale 'absorbed docs/metabot-core/' README note + removed empty dir; fixed dangling docs/internal/web-ui-arch.md pointer in server.ts. Verified: root+server+web-ui typecheck clean; 594 bridge + 359 server tests pass. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
GitHub-only commits #312 (ExitPlanMode auto-approve) and #314 (full agent activity content) are already present in this branch via their internal equivalents (exit-plan-mode.ts is byte-identical; #314 = internal 8316195). GitLab is the source of truth, so this branch's tree is kept verbatim (-s ours); this merge only makes GitHub main an ancestor so the cutover is a clean fast-forward.
The synced packages/cli + metamemory + skill-hub test suites import the
compiled entry of @xvirobotics/cli-core (exports -> dist/index.js). CI ran
`tsc --noEmit` (no emit) then `npm test`, so those imports failed to resolve
('Failed to resolve entry for package @xvirobotics/cli-core'). Add a build
step for the three thin libraries before tests. Pre-existing gap surfaced by
the workspace tests this sync brings to GitHub; not a content regression.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Brings GitHub `main` up to date with the internal source of truth and turns this repo into the self-hostable personal edition.
What this does
Verification
🤖 Generated with Claude Code