mirror of
https://github.com/lobehub/lobehub.git
synced 2026-09-20 04:56:13 +08:00
fix/refer-topic-message-rendering
18 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
8b7a674d22 |
✨ feat(cli): add lh doctor to diagnose the whole chain in one command (#19626)
* ✨ feat(cli): add `lh doctor` to diagnose the whole chain in one command Diagnosing a broken `lh` today means knowing which layer to suspect: `status` answers only "does the gateway accept this machine", `whoami` only "who am I". Everything else — a node too old for the CLI's own imports, a device gateway that belongs to another deployment, a workspace scope saved under a different account, a provider with no key — surfaces much later as an error that names the symptom rather than the cause. `lh doctor` walks the chain in order and stops reporting derived symptoms once it finds a root cause: runtime → endpoints → credentials → scope → run context → server → device → execution - Profiles: bare `doctor` runs the core layers (fast, cheap); `--profile connect | agent | hetero | selfhost | all` adds the deeper ones. - `--offline` skips everything that touches the network, `--deep` opts into the costly checks, so a diagnostic never spends money by default. - `--json` prints the same report object the terminal renderer consumes, one entry per check with `status`, `detail`, `evidence`, `fix` and — for skips — the upstream check or flag that excluded it. - Exit code contract: 0 unless something failed, `--strict` promotes warnings. - `--fix` applies only the repairs a check knows how to make (stale workspace scope, unparseable settings.json, loose permissions, leftover daemon state) and re-runs the check so the report shows the repaired state. - `--deep --agent <id|slug>` starts a real run and reports tokens and cost, proving the chain end to end instead of inspecting it. Also extracts the credential precedence (`LOBEHUB_JWT` > explicit flags > `LOBEHUB_CLI_API_KEY` > stored login) into `pickAuthSource`, shared by `resolveToken`, the tRPC client and doctor — it was spelled out twice and had already drifted on which env names it accepts. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * 🐛 fix(cli): stop doctor from leaking proxy credentials or repairing healthy state Five findings from an independent review of the doctor series, all real: - `--fix` deleted the pid/status files of a daemon that was merely reconnecting, orphaning a live process `connect stop` could no longer find. Repairs now run only for the finding a check marks `evidence.repairable`, and each repair re-checks which state it was handed. The runner skips repairs entirely for findings without that marker, so a warning with nothing to undo no longer reports a failed repair. - Proxy env vars were copied into the report verbatim. `http://user:pass@host` is a normal way to configure one, and the report is meant to be pasted into an issue — URL credentials are now redacted. - A self-hosted server with the default device gateway failed `endpoints.resolution`, which skipped reachability, credentials, identity and every agent check behind it. An installation that only uses the HTTP API needs no device gateway, so this is a warning; the device profile's own handshake still fails concretely. - `--fix` dropped a valid saved workspace scope whenever `LOBEHUB_WORKSPACE_ID` overrode it for one run, silently moving the user to personal scope once the override went away. - `getAiProviderList` reports a provider's toggle, not whether a key is stored, so an enabled-but-keyless provider was reported as ready and the run then died with `InvalidProviderAPIKey` — the exact failure the check exists to pre-empt. Agent readiness now reads that one provider's key vault and names where the key comes from, saying "not verified" rather than passing when a restricted API key cannot see vaults at all. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * 📝 docs(cli): record why the node check cannot catch a too-old runtime Closing that gap means changing how the CLI is launched, which is not this command's to change. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * 🐛 fix(cli): keep doctor from crashing, over-claiming, or leaking identifiers Ten findings from a second independent review, all confirmed: - A socket failure reaching the gateway arrives as an EventEmitter `error` event, which `connect().catch()` cannot see. With no listener the whole report died with "Unhandled error" instead of reporting a failed handshake — verified by removing the new listener and reproducing the crash against a refused port. - Server, gateway and proxy URLs are redacted at the report boundary, not just in the proxy variable: a basic-auth URL in `LOBEHUB_SERVER` used to print its password in `--json`. Gateway error text also quoted the WebSocket query string, carrying hostname, device id and user id; the account email is now masked too. The raw values still flow to whatever connects with them. - "Enabled on the server" no longer claims a key exists. `deepseek` is enabled unconditionally in the server's own config, and `ENABLED_OPENAI` / `ENABLED_OLLAMA` default to true, so the report now says the server reports it enabled and that server-side keys are invisible from here. - A provider holding only a `baseURL` is a local runtime (Ollama, LM Studio), not a missing key: it warns instead of failing, so it no longer blocks the deep round trip through `dependsOn`. - The round trip only reads "no longer tracked" as success once it has seen the run live. The server also answers null when there is no state at all, which made the check incapable of failing. - A saved workspace scope is only repairable when the account is identified and differs. Under `LOBEHUB_CLI_API_KEY` there is no readable subject at all, and `--fix` was deleting a selection that was still valid for the stored login. - The settings repair no longer overwrites an existing `.bak`. - The update check bounds its own registry request: the runner's timeout would otherwise surface as a failure and a non-zero exit, for something this check only ever reports as a warning. - The command sets `process.exitCode` instead of calling `process.exit`, which does not wait for stdout to drain and can truncate a piped JSON report. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * 🐛 fix(cli): redact the server URL the workspace-scope check reports Sweeping every check's evidence for an injected credential — the way the acceptance round does — turned up one the previous pass missed: the scope check copies the resolved server URL verbatim, so a basic-auth self-hosted URL put its password into `--json`. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * 🐛 fix(cli): make the URL redactor linear instead of backtracking CodeQL flagged js/polynomial-redos: matching the URL lazily and then requiring a `?` backtracks across the remainder of the string at every starting position, so an error message with many `ws://` runs takes quadratic time. Tokenise first and decide per token — same output, no backtracking, with a test that fails if the quadratic shape comes back. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * 🐛 fix(cli): scrub the doctor report centrally and stop it lingering after exit Seven findings from a third independent review, all confirmed: - Redaction had leaked in a third place: Node's fetch error quotes the full request URL, and capability and daemon evidence copied gateway URLs as-is. Per-check redaction had now missed an instance in each of three rounds, so the runner scrubs every result — detail, fix, evidence, repair messages — before it enters the report: URL userinfo, WebSocket query strings and email addresses, by linear token scanning rather than a backtracking pattern. - With `process.exit` gone, the process waited for the event loop to empty, so a request abandoned by a check timeout, or the registry check's uncleared timer, kept it alive — up to the full `--timeout` after the report printed. The command now exits from a stdout write callback: after the output has drained, and no later. - The registry check clears its timer and gets a runner budget that always outlasts its own deadline; a sub-second `--timeout` no longer turns its warning into a failure. - A settings.json that cannot be read is no longer classified as corrupt and offered to `--fix`, which moved a possibly valid file aside unparsed. The repair also re-confirms the parse failure before moving anything. - A run that stops being tracked is inconclusive even after it was seen running: the server returns null for lost state too, so only `isCompleted` proves the round trip. - A live daemon that has not reported a connection state is a warning, not "connected as device unknown". Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
b1a9979c5f |
✨ feat(cli): publish a local HTML file as an artifact (#19360)
* ✨ feat(cli): publish a local HTML file as an artifact `lh artifact publish <file>` gathers the page's local references, packs them, and publishes the result. The page's identity is a deployment id, recorded in `.lobehub/artifacts.json` next to the project and committed with it, so a publish from CI updates the same site a developer created locally. Republishing adds a revision and keeps the public URL; `--new` makes a separate site and rebinds; `--deployment <id>` targets one for a single run without touching the manifest. A bound id that no longer resolves is an error — falling back to create would silently strand the URL the caller meant to update. Assets are read straight off the filesystem, so `--root` (default: the working directory) sets how far up a page may reach for them. That replaces the app's sandbox boundary, which does not apply here: the CLI reads with the user's own permissions, and a page under `dist/` referencing `../assets/logo.png` is ordinary rather than an escape attempt. A create carries an idempotency key, written to the manifest *before* the request. If the response is lost, the retry reuses the key and the server returns the deployment it already made instead of minting a second site and burning another quota slot. Updates are addressed by id and deliberately carry no key: a retry costs a spare revision, never a stray site. Claude-Session: https://claude.ai/code/session_01558yVdNvoxocvnWAs11DGT * 📝 docs(acceptance): record what the CLI artifact-publish round had to learn Two judgment rules and two probe recipes from verifying `lh artifact publish` against a local cloud → market → artifact-host chain. L-E21 is the one an acceptance-checker caught mid-round: the case asserting `--new` leaves the original site untouched published two sites with identical bytes and a request line that echoed only the response. It read as a pass while proving nothing, because requesting the wrong site would have printed the same output. The rule generalises — make the two objects distinguishable before capturing, and echo the target in the request. L-S22 covers the quota the round consumes itself: each publish leaves an active deployment, the Free tier caps at three, and the third run fails in a way that reads as a regression. Compounded by clearing the cap for the wrong account — the CLI publishes as the seeded runtime user, not the smoke's default id. P84 records how to authenticate the CLI against lobehub-cloud's dev runtime at all (seed an api_keys row; `dev:runtime:auth` only writes browser cookies), plus the port-override that every runtime subcommand needs. P85 records how to simulate a publish whose response was lost, which cannot be produced by killing the process because the manifest is what carries the recovery state. Claude-Session: https://claude.ai/code/session_01558yVdNvoxocvnWAs11DGT * 🐛 fix(cli): register @lobechat/html-artifact in the CLI's own workspace `apps/cli` installs standalone against its own `pnpm-workspace.yaml`, which enumerates the packages it may link rather than globbing them. The root install resolved the new dependency, so this only showed up as CI's "Install CLI deps" step failing: "@lobechat/html-artifact@workspace:*" is in the dependencies but no package named "@lobechat/html-artifact" is present in the workspace Claude-Session: https://claude.ai/code/session_01558yVdNvoxocvnWAs11DGT * 🐛 fix(desktop): register @lobechat/html-artifact in the desktop workspace too `apps/desktop/pnpm-workspace.yaml` lists `../cli` as a member, so the CLI's new dependency has to be resolvable from the desktop install as well — its "Install deps" step failed the same way the CLI's did. Unlike the CLI workspace this one sets `lockfile: true`, so the regenerated lockfile ships with it. Claude-Session: https://claude.ai/code/session_01558yVdNvoxocvnWAs11DGT |
||
|
|
6430e476bd |
✨ feat(cli): add lh workspace commands and a persisted workspace scope (#19015)
* ✨ feat(cli): add `lh workspace` commands and a persisted workspace scope The CLI could only reach workspace content through the `LOBEHUB_WORKSPACE_ID` env var, so `lh agent view <workspace agent>` reported "Agent not found" with no way to discover the workspace id or switch into it. - `lh workspace list|view|current|use|create|update|settings|stats|usage| members|invite|invitations|audit-log` - `lh workspace use <id|slug>` persists the scope in `~/.lobehub/active-workspace`, slotting below `LOBEHUB_WORKSPACE_ID` so a one-off run can still override it - `lh whoami` reports where the active scope came from - typed workspace stubs in business-server so the OSS contract type-checks; cloud keeps supplying the real implementations No delete/remove commands are exposed. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * 🐛 fix(cli): make the workspace scope report match the scope commands use Codex review follow-up. - `workspace use --personal` warned nothing while `LOBEHUB_WORKSPACE_ID` was set, so it printed "Scope set to personal" while every later mutation still hit that workspace; both `use` paths now share one override warning - `workspace list` starred the persisted workspace instead of the effective one, marking the wrong row whenever the env var pointed elsewhere - `workspace current` reported every failure as lost access, hiding auth and network errors; it now surfaces the real reason, and flags an id the server answers with `null` instead of reporting a typo'd scope as healthy Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * 🐛 fix(cli): bind the persisted workspace scope, and align the OSS stub contract Codex review follow-up. Scope no longer survives an identity it does not belong to. `active-workspace` now stores the account fingerprint and server URL the scope was chosen under; a mismatch is reported as stale and drops to personal instead of attaching an `X-Workspace-Id` the current identity has no membership in — which cloud answers by silently downgrading to personal, so writes were landing on personal data while every status line still said "workspace". `logout` clears it. Stub contract now mirrors the cloud router it stands in for: - slug gets the same length and format rules (new `@lobechat/business-const` workspaceSlug, minus the cloud-only route reservations) - `getStatistics` is admin-gated, `getSettings` / `getMyStatistics` require the workspace header, and the member/audit procedures use the compat + role gate cloud uses rather than a stricter one Also: - `workspace invitations` counted expiry through `timeAgo`, printing "-604800s ago"; added `timeUntil` - `workspace stats` is admin-only, so a member hit a bare FORBIDDEN — it now points at `--mine`, and lets unrelated failures through - `workspace create --use` was the one scope-setting path missing the `LOBEHUB_WORKSPACE_ID` override warning - documented the command group, the scope precedence and `active-workspace` in the cli skill Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * 🐛 fix(cli): derive the scope identity from public claims, not the API key CodeQL flagged `js/insufficient-password-hash` (high): the API-key branch digested the key with a single unsalted SHA-256 round and wrote 64 bits of it to `~/.lobehub/active-workspace`. The digest bought nothing — an API key carries no readable subject, so there is no account to bind to either way. Identity now comes only from the `sub` claim of a JWT, and API-key auth returns no identity at all: `workspace use` refuses to save under it and points at LOBEHUB_WORKSPACE_ID. Resolution order also had to mirror `getAuthAndServer`, which prefers an API key over the stored login. Reading past the key to the stored credentials bound the scope to one account while the request authenticated as another — the exact mismatch this binding exists to catch. Each stale reason now carries its own message rather than one guess. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
77be8bb286 |
✨ feat(cli): lh trace op — inspect and replay a recorded operation, single call or whole trajectory (#18797)
* ✨ feat(cli): add `lh trace replay` to re-issue a frozen LLM call across models An execution snapshot already freezes everything a single LLM call saw — `steps[].contextEngine.output` is the exact message array sent to the model, `context.payload.tools` the toolset it could reach — but nothing consumed it. Diagnosing a bad answer meant re-running the whole agent, which re-assembles context and so mixes the harness and the model into one unattributable result. `lh trace replay` sends that frozen payload back out with only the model swapped, so a difference in output is attributable to the model. If every model fails the same payload, the context assembly is at fault; if some pass, it is model selection. - `packages/agent-tracing`: extract the local → `_remote` cache → download resolution that inspect / resolve / ctx-lint each reimplemented into a shared `loadSnapshot()`, and export it plus the remote-store helpers. Downloads stay opt-in so read-only callers never hit the network. - `apps/cli`: new `trace` command group with `replay`, defaulting to the last `call_llm` step and to the model the snapshot ran on. - `--judge <criteria>` scores each output through `@lobechat/eval-rubric`'s `llm-rubric` matcher, so replay scoring is the same code path the eval pipeline uses — only the transport is local. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> (cherry picked from commit |
||
|
|
bc08dbe17d |
♻️ refactor: make API key prefix and CLI product naming distribution slots (#18637)
* ♻️ refactor(cli): collect every product-naming string into one module
The CLI's identity was spread across a dozen files: bin names in
`utils/completion.ts`, the config directory duplicated in five places, the
systemd unit name defined twice, the product name in a handful of command
descriptions, and — least visibly — inside `buildNotifyProtocol`, a prompt
handed to heterogeneous agents that the model then repeats back to the
user.
That is fine for a single distribution and leaves a white-label build with
no seam. Several of these are reached at runtime along paths no UI gating
can intercept, and one of them is a prompt, so there is nothing to gate.
`src/constants/identity.ts` now holds all of them. Values are upstream's,
unchanged, and the default build's observable output is identical —
verified by building with this repo's own config and diffing what the CLI
prints:
Usage: lh [options] [command]
LobeHub CLI - manage and connect to LobeHub services
complete -o nosort -F _lobehub_completion lh lobe lobehub
lh - LobeHub CLI - manage and connect to LobeHub services
Two things that were already wrong, fixed on the way:
- `skillLocator.ts` compared `pkg.name` against a hardcoded
`'@lobehub/cli'`, so the package-root walk breaks for any fork or
rename. It reads `cliPackageName` now, which is the same value from
the same package.json.
- `constants/auth.ts` exposed a single API-key env var name. It is a
list now, most-preferred first, so a distribution can introduce its
own prefix without breaking a variable a user already exported. Only
USER-set variables moved; `LOBEHUB_SERVER`, `LOBEHUB_JWT` and the
agent-run ids are injected by the desktop app and the server, so
renaming those needs all three sides to move together and is left
alone.
`man/roff.ts` also stops hardcoding `LH` and `@lobehub/cli` in the roff
header, taking them from the bin name and `pkg.ts` instead — the man page
described a command that would not exist under any other name.
* ♻️ refactor(api-key): make the key prefix a slot instead of a literal
`sk-lh-` was hardcoded in four places and asserted in four test files. It
is the one brand string a deployment's end users actually handle — they
copy it out of the UI, paste it into CI config, quote it in support
threads — so on a white-label distribution it undoes the rebranding
everywhere else.
It is also load-bearing rather than cosmetic. `validateApiKeyFormat` is
what the OpenAPI auth middleware uses to tell an API key from an OIDC
JWT, so generation and validation must read the same value or every
issued key fails auth as a malformed JWT. That is why the prefix is one
constant now, used by both.
`API_KEY_PREFIX` defaults to `sk-lh-`, so upstream's behaviour is
unchanged — verified by generating a key and round-tripping it through
the validator.
Two deliberate choices in the implementation:
- Validation compares the prefix with `startsWith` and tests only the
16-character suffix against a static pattern, rather than
interpolating the prefix into a RegExp. The prefix is data flowing
into a pattern, and escaping it correctly is a thing to get wrong;
this way there is nothing to escape and the check stays correct for
whatever prefix a distribution picks.
- The tests derive their fixtures from the constant instead of
hardcoding a spelling, so they pass for any prefix. Several of them
fed a literal straight into `validateApiKeyFormat`, so they would
have gone red the moment anyone overrode it — failing on the
override rather than on a real defect.
`spec.ts`'s `bearerFormat` follows the constant too; it is the public
API document, so it was advertising a format the deployment no longer
issues.
* ♻️ refactor(cli): collect the completion word-index env var into identity.ts
CLI_COMPLETION_CWORD_ENV joins the other product-naming strings in
identity.ts. `LOBEHUB_COMP_CWORD` was the one string the earlier
identity refactor (
|
||
|
|
4f664f1aa0 |
✨ feat(goal): add CLI-driven goal graph runtime (#18597)
* ✨ feat(goal): add CLI-driven goal graph runtime * 🐛 fix(task): defer self completion until run finishes * 🐛 fix(goal): serialize coordinator mutations * 🐛 fix(goal): bound responsible task descriptions * 🐛 fix(goal): categorize API key scope --------- Co-authored-by: Arvin Xu <arvin.x@lobehub.com> |
||
|
|
4c0ff6614f |
✨ feat: implement Project backend and CLI (#18006)
* ✨ feat: implement project backend and CLI * 🐛 fix: enforce project task boundaries |
||
|
|
306e971505 |
✨ feat(acceptance): screenshot attachments in feedback, decision-bar redesign & frosted dialogs (#17303)
* ✨ feat(acceptance): screenshot attachments in feedback, decision-bar redesign & frosted dialogs Follow-ups to #17291 (topic 验收体验优化与界面改版需求): **Attach screenshots to reject / group feedback (C19)** - Reviewers can paste or upload their own screenshots when rejecting a check or leaving group feedback — the "here's what I'm seeing" channel that annotating the agent's evidence didn't cover. A pasted image on the note textarea uploads straight through the file store (real file rows, referenced by id, same flywheel as evidence — never a base64 blob on the note). - `fileIds` land on `VerifyCheckDecisionDetail` / the group-feedback entry (typed jsonb, no migration); `getBundle` resolves them to URLs with the same owner-scoped resolver the evidence uses. Rendered on the reject feedback card, the group-feedback card and in the feedback clearing-list drawer; read-only thumbs zoom on click. - A screenshot-only reject is now valid feedback (server refine + client canSubmit accept fileIds). **DecisionBar redesign** — the floating strip was too plain: a tinted state rail + soft status disc, tighter hierarchy, a filled feedback chip. **Frosted dialogs** — the acceptance decision dialogs (reject / group feedback / accept) blur the page behind them via per-modal `styles.backdrop`, matching the antd modal mask. Deliberately not a global backdrop rule: base-ui popups (Select/Menu lists) share the same `role=presentation` element and would frost their own content. New i18n under `acceptance.review.attach*` (en-US + zh-CN hand-written; run `bun run i18n` to fill the rest before release). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * 💄 style(acceptance): group-feedback entry by the chevron, quiet iteration chips, ledger-list feedback drawer Review feedback from the acceptance page (r4 actionable items): - group-feedback entry moves next to the collapse chevron in the group header's right control cluster, and uses the comment-semantic MessageSquareText (the + icon read as "add check"). - a check the user already accepted stays folded: the first-load seed skips accepted rows even when they carry visual evidence, and the group-level expand-items toggle only opens rows still awaiting judgment. - the "introduced in round N" chip is detail behind the "iterated N×" headline — collapsed to zero width until the chip pair is hovered. - the feedback drawer drops per-entry card chrome for a hairline ledger list: icon + title + meta on one line, comment and attachments indented beneath. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * 🐛 fix(acceptance): float the round ledger over the report on narrow viewports Below the lg breakpoint an in-flow 300px+ ledger column crushed the report into a ~100px sliver (vertical one-character-per-line text). The ledger now follows the list panel's narrow regime (useReportPanelExpand): entering the narrow viewport collapses it, and reopening renders a 340px float overlay pinned to the right edge — the report keeps its full width beneath. Wide viewports keep the in-flow resizable panel unchanged. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * ✨ feat(acceptance): repair dispatch from the decision bar, review-progress ring & r2 review fixes Review feedback (r2 actionable) + the repair-loop features: **Decision bar** - review-progress ring (user-confirmed / total reviewable) with wording 「已完成 X / N 项验证项,剩余 M 项等待你的验收」; the color rail is gone (review: current style stands without it). - state-driven actions: queued feedback → 「复制 review 建议」(fill) + 「打回重跑」(primary); clean review → 「评论并打回」(text) + 「接受交付」 (fill), accept gaining primary weight only once every check is confirmed. - 打回重跑 dispatches the hardcoded repair prompt straight into the origin conversation via agentNotify.notify (the lh-notify callback channel) — the agent reads the feedback itself through the CLI; 复制 review 建议 puts the same prompt on the clipboard for any agent. **Review fixes** - reject/group-feedback modals: the attachment strip + picker hug the note input in one row (was drifting far below). - iteration chip collapses to [↻ N]; "verified N rounds · introduced in round X" moved into its tooltip. - feedback drawer rows drop the per-row icon (all identical) and the seq label steps up to secondary. - narrow-viewport ledger: custom overlay → masked Drawer (click-outside + ESC dismiss), so it no longer reads as blocking the report. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * ✨ feat(acceptance): portal title menu, embed de-noising, one-line compact filter & first-class lh acceptance Portal embed (inline) polish: - the title gains a `…` menu holding copy-link and refresh (refetches the bundle); the standalone copy icon leaves the header. - the origin row (agent + topic chips) hides in the embed — that surface already lives inside the origin conversation. - container-narrow surfaces (portal embed, sub-lg viewports) swap the filter Segmented for a compact filled Select so the checklist toolbar holds one line. CLI: `acceptance` becomes a first-class command group (`lh acceptance list|view|feedback|accept|reject`) — the review loop's front door, not a verify implementation detail. New `feedback` subcommand prints the full review trail (check rejects with region notes and attachment ids PLUS group-scoped feedback, which the old view omitted), `--actionable` filters to what the next repair round must act on, `--json` for programmatic use. `lh verify acceptance` stays as a deprecated alias. The in-app repair prompt now points agents at `lh acceptance feedback <id> --actionable`. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * 💄 style(acceptance): fullscreen annotate redesign, image aspect-ratio, decision-bar states & feedback polish Fullscreen annotate stage: - layout re-anchored: hint moves out of the header into the region-comments panel (with the comments it produces), zoom becomes a floating pill over the stage, thumbnails on top. - taller/wider (94vh × min(98vw,1680px)). - explicit Done / Cancel footer — Cancel restores the annotation snapshot taken when the stage opened, so closing is no longer an ambiguous X. Evidence images reserve their box from the upload's stored width/height (surfaced through the bundle's file resolver) — expanding a check row no longer jumps in height while the screenshot streams in. Decision bar & feedback: - feedback card / drawer labels drop "Your feedback · round N" for a plain "Feedback" (round lives on the timestamp already). - progress ring reads as a dashed grey "not started" circle at zero, with a zero-state line "N checks awaiting your review"; a fully-confirmed review shows the BadgeCheck disc, not a maxed dial. - 打回重跑 stamps the aggregate `repairing` (new acceptance.markRepairing) so the send-back visibly takes effect; the copy-review entry stays available in the repairing state. - copy / send-back toasts surface bottom-center, above the bar they came from. - feedback drawer rows get roomier vertical rhythm; narrow-mode ledger drawer drops its inner collapse icon (the Drawer already closes). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
2aabda5281 |
♻️ refactor(cli): tool worker isolation (#16200)
* 🔧 isolate cli local-system tool workers * ♻️ drop unrelated connect changes * ♻️ split cli tool worker isolation helper * ♻️ tighten cli tool worker entrypoints * ♻️ simplify cli isolated worker streams * ♻️ simplify cli isolated worker output handling * ♻️ simplify cli tool worker command flow * ♻️ extend cli isolated worker coverage * 🧪 align cli tool dispatch tests with worker routing * ✅ cover cli isolated worker error handling --------- Co-authored-by: Ubuntu <ubuntu@localhost.localdomain> |
||
|
|
2487407192 |
✨ feat(cli): add lh update command to self-upgrade the CLI (#16052)
* ✨ feat(cli): add `lh update` command to self-upgrade the CLI Add a `lh update` command that checks the npm registry for the latest published version of `@lobehub/cli`, compares it against the installed version, and runs the matching global-install command. The package manager is auto-detected (npm/pnpm/yarn/bun) from the running binary path, with `--package-manager` to override. Supports `--check` for a dry run and `--tag` to target a non-latest dist-tag. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * 🐛 fix(cli): resolve update command package metadata from the bundled entry `update.ts` loaded `require('../../package.json')`, a path relative to its source location. tsdown bundles everything into `dist/index.js`, where that runtime require resolves to `node_modules/@lobehub/package.json` instead of `@lobehub/cli/package.json` — so a published build threw at startup the moment the update module was registered, breaking every `lh` invocation. Reuse the single package-metadata load already done by the bundled entry (`program.ts`, at the depth where `../package.json` is correct both in source and in dist). Export the package name alongside the version and consume both in the update command. Verified against a real `tsdown` build: `lh --version` starts cleanly and `lh update --check` resolves `@lobehub/cli` correctly. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * ♻️ refactor(cli): extract package metadata into a shared src/pkg.ts const Importing `cliVersion`/`cliPackageName` from `program.ts` into `update.ts` created a `program ↔ update` import cycle (program registers the update command). Move the single `require('../package.json')` load into a dedicated `src/pkg.ts` const module that both `program.ts` and `update.ts` import, breaking the cycle. The module sits directly under `src/` — the same depth as the bundled `dist/index.js` — so `../package.json` resolves to `@lobehub/cli`'s own package.json in both source and bundled runs. Verified against a real tsdown build: one `createRequire("../package.json")` remains, `lh --version` starts cleanly, `lh update --check` resolves the package, and `man:generate` still reads the version via the re-export. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * 🐛 fix(cli): use the semver package for update version comparison The hand-rolled comparator ordered prerelease identifiers lexicographically, so `1.0.0-beta.10` sorted below `1.0.0-beta.9` and `lh update --tag beta` reported "already current" instead of offering the newer prerelease. Replace `compareSemver` with `isNewerVersion`, delegating to the `semver` package (already a workspace dependency) so numeric prerelease parts compare numerically. Add `semver` / `@types/semver` to the CLI package and a regression test for the beta.9 → beta.10 case. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
dbf743cc12 |
✨ feat(verify): Agent Run delivery checker system (#15489)
* 🗃️ feat(database): add verify system tables for agent run delivery checker Implement the database layer for the Agent Run delivery checker (Verify System). Reuse / definition layer: - verify_criteria: a single reusable pass/fail standard (atomic unit), carrying its verifier config + onFail default and bound to a document for judging guidance (iteration history reuses document_history; no version columns) - verify_rubrics: a named group that aggregates criteria — the reusable unit - verify_rubric_criteria: junction, which criteria a rubric aggregates (criteria are reusable across rubrics) Mounted onto an agent via the existing agency config jsonb: - agencyConfig.verifyRubricId: a reusable rubric (criteria template) - agencyConfig.verifyCriteriaIds: ad-hoc one-off criteria A run's plan instantiates the union of both. No dedicated bindings table. Snapshot + result layer: - agent_operations.verify_plan (jsonb) + verify_plan_confirmed_at: the per-run immutable check-item snapshot lives ON the operation (1:1 — auto-repair spawns a new operation), instead of a separate plans table - agent_operations.verify_status: denormalized rollup for list-page badges - verify_check_results: per-criterion result with the Toulmin model (verdict/confidence as columns, narrative in a typed toulmin jsonb), N:1 verifier_tracing_id for batch judging, FP/FN flags for the data flywheel; relates to the plan via operation_id + stable check_item_id Ref: LOBE-10019 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * ✨ feat(verify): add Agent Run delivery checker backend + frontend module Implements the verify system on top of the schema (PR #15480): - models: verifyCriterion / verifyRubric (+junction) / verifyCheckResult; agentOperation verify plan/status methods - services/verify: AI plan generation (auto-create criteria), executor with LLM Toulmin judge (per-criterion + batch), program placeholder, agent & auto-repair spawner seams, rollup chokepoint, feedback fp/fn, completion lifecycle bridge - lambda verify router (criteria/rubric CRUD, plan, results, feedback) - frontend feature module: service, SWR hooks, CheckerDock state machine, RunArtifact, verify i18n namespace - tracing scenarios: VerifyPlanGen / VerifyJudge Live UI mount (dock/artifact into chat) pending server operationId source. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * 🐛 fix(verify): persist delivery-checker verdicts via async tracing backfill The LLM judge produced valid verdicts but they were never persisted, leaving every run stuck at `verifying`. Two root causes: 1. FK ordering: `writeVerdict` stamped `verifier_tracing_id` synchronously, but the `llm_generation_tracing` row is written asynchronously (best-effort, after the response) — so the hard FK was violated every time and the verdict write was rolled back. Now the verdict is written with a null link, and the tracing id is backfilled by an `onPersisted` callback that fires only after the tracing row commits (still non-blocking). If tracing is disabled the link simply stays null. 2. Verdict parse: the judge JSON schema is non-strict, so the provider returns optional Toulmin fields as explicit `null`. The Zod validator used `.optional()` (accepts undefined, not null), so any null failed the whole `safeParse` and discarded the batch. Switched to `.nullish()`. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * ✨ feat(cli): add `verify` command for the delivery checker Adds `lh verify` covering the full delivery-checker chain — criteria & rubric CRUD, per-run plan (generate/state/confirm/skip), execute (LLM judge), results, and feedback — calling the `verify` lambda router. Enables end-to-end backend testing of the verify system. Also adds the missing `tool-runtime` / `prompts` / `const` workspace entries to the CLI's `pnpm-workspace.yaml` so the standalone package installs. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * 💄 feat(verify): add verify message role + delivery-checker card UI Make the delivery-checker renderable in chat: - Fix the `features/Verify` components so they compile: flatten the `verify` locale to the repo's flat-dotted-key convention (keySeparator: false), import `Flexbox`/`TextArea` from `@lobehub/ui` (react-layout-kit is no longer a dep), and the token cast. - Add a `verify` UI message role + a `VerifyMessage` card that renders the Run Artifact + checker dock from `metadata.verifyOperationId`, wired into the message renderer switch. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * ✨ feat(verify): add lobe-agent `generateVerifyPlan` tool (server runtime) Lets an agent set up the delivery checker for its run: the agent calls `generateVerifyPlan` early (per the new `<delivery_checker>` system-role guidance), which instantiates the rubric / ad-hoc criteria into a frozen plan on the current `agent_operations` row. Executed server-side only — the executor is dispatched via `runtime[apiName]` with `operationId` threaded through the tool execution context; the client `BaseExecutor` gracefully no-ops it. Also registers the metadata fields (`verifyOperationId`/`verifyRound`) on the message metadata zod schema so the role='verify' card can carry its operation id. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * ✨ feat(verify): surface role=verify card on run completion (LOBE-10051) Connect the delivery checker to the conversation: when an Agent Run with a verify plan completes, `CompletionLifecycle` inserts a persisted `role='verify'` message (parented to the assistant, carrying `metadata.verifyOperationId`) that renders the checker card. Self-guarded — no plan → no card, failures never affect the run. `role='verify'` behaves like a `user` leaf message everywhere it flows (persistence + conversation-flow pass it through unchanged); only the context-engine treats it specially: a new `VerifyMessageProcessor` drops it from the model context (UI-only card, not a valid model role). Adds `verify` to `CreateMessageRoleType`. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * 💄 feat(verify): merge run-artifact + checker into one card The role=verify message rendered two stacked cards (Run Artifact summary + Delivery Checker) that duplicated the check-item list. Merge into a single card: the `Run Artifact · Round N` header, then the checker results + actions, then the snapshot note. RunArtifact/CheckerDock gain an `embedded` prop (header-only / body-only, no card chrome) and VerifyMessage composes them under one border. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * ✨ feat(verify): derive generateVerifyPlan rubric from agencyConfig A real agent calls `generateVerifyPlan` with just a `goal` and doesn't know rubric ids. When `rubricId`/`criteriaIds` params are absent, derive the mounted rubric + ad-hoc criteria from the executing agent's `agencyConfig.verifyRubricId / verifyCriteriaIds`. Params still win when given. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * 🐛 fix(cli): surface agent gateway WebSocket close code + reason The `onclose` handler logged `String(event)` → the useless "[object CloseEvent]". Surface `event.code` (+ `event.reason` when present) so a gateway disconnect before completion is actually diagnosable. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * 💄 fix(verify): rename "Run Artifact" → "Verification", drop failed red border - The kicker said "Run Artifact" — it's automated verification, not an artifact. Renamed to "Verification · Round N". - Removed the red error border on a failed check — a normal card reads better. - Fixes a render crash (`useVerifyState is not defined`): the border removal left a dangling reference after the import was dropped. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * ✨ feat(cli): poll run status when the agent stream drops When the live stream (gateway WebSocket / SSE) closes before the run finishes, the run is still executing server-side — so instead of hard-exiting, fall back to polling `aiAgent.getOperationStatus` every 10s until the run reaches a terminal state (or is no longer tracked). Pairs with surfacing the WS close code/reason. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * 💄 feat(verify): add Render for generateVerifyPlan tool call The generateVerifyPlan tool call rendered as the default param/result dump. Add a Render that lists the generated delivery checks (title + gate/auto-fill tag), and surface the items on the tool state so the Render can read them. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * ✨ feat(verify): auto-confirm generated plan so checks run on completion The agent generated a plan but it stayed `planned`/unconfirmed, so the completion hook (which gates on a confirmed plan) never ran the checks — the card was stuck at "awaiting confirmation" with no pass/fail. In the headless agent flow there's no one to click Confirm, so `generateVerifyPlan` now auto-confirms the plan it generates; the checks then run automatically on completion. (An interactive "review before run" gate is a future enhancement.) Also: the verify card header disappeared in the draft/planned phase (`phaseToArtifact.draft` was null). Give it a header so the card always shows its "Verification · Round N" heading. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * 🐛 fix(agent-tracing): only count opaque/presentational attrs as structural noise The first structuralNoiseRatio charged ALL markup (every <...> tag) as noise, which over-penalized legitimately structured results 3x. Grounding against real web-search output (`<item title="…" url="…">snippet</item>`) showed the tags and the title=/url= attributes ARE the signal the model reads. Now only opaque/presentational attribute names (id, class, style, data-*, aria-*, role, on*) count as noise; semantic element tags and content-bearing attributes (title, url, href, name…) are kept. On a 57-op user-interrupted sample this drops web-search noise 42%→0% and overall estimated waste 16%→5%, leaving large-payload (readDocument) and high error-rate tools as the real signal. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * ✨ feat(verify): model-authored criteria with name/description/instruction-in-document + agent verifier Restructure the generateVerifyPlan tool to a createDocument-style full-create flow and wire up the agent verifier path: - criteria now = title + description (required one-liner) + instruction (required detailed rubric); instruction lives in a linked document (verify_criteria.documentId), description is a new verify_criteria column (migration 0111). verifierConfig no longer holds description/instruction. - generateVerifyPlan creates verify_criteria + a rubric, snapshots the plan onto the operation and confirms it; judge resolves the instruction from the document. - agent-type checks run as verifier sub-agents (execAgent + isolated thread) whose onComplete hook parses a VERDICT and writes it back to verify_check_results (renamed AgentVerifierSpawner → VerifierAgentRunner). - UI: custom Inspector for the tool header; check list shows per-verifier-type icons (llm/agent/program) + description + required/optional tag; i18n en/zh. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * ⚡️ perf(verify): run program/llm/agent checks concurrently on completion The three verifier kinds are independent; previously the agent spawn waited for the batched LLM judge to finish. Run them via Promise.all so agent sub-agents start immediately alongside the LLM batch. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * ✨ feat(verify): dedicated builtin verify-agent + writeback tool, role=verify message, portal check editor - Add `@lobechat/builtin-tool-verify` (submitVerifyResult) + builtin `verify-agent`; agent-type checks now run as the dedicated verify agent (not the user's agent), which investigates and writes its verdict back via the tool during its run. - Verifier inherits the parent run's model/provider (builtin default may be unconfigured locally). - role=verify completion message no longer requires an assistantMessageId, so the delivery-checker card always surfaces when a plan exists. - Portal editor for verify checks (title/description/instruction/verifier/onFail). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * 🐛 fix(verify): restrict verify-agent to its writeback tool; fix running loader icon Root cause of stuck `running` agent checks: the verify-agent ran in agent mode and inherited all default tools (web-browsing, cloud-sandbox, skills, activator), so it went off web-searching/crawling to "investigate" and never called submitVerifyResult. - Run the verify-agent in chat mode (enableAgentMode: false, searchMode: off) — the strict whitelist — and whitelist `lobe-verify` for chat mode so the verifier gets ONLY its writeback tool. - Sharpen the verify systemRole: judge from the provided deliverable/instruction (no external tools), always reach a verdict, and always call submitVerifyResult. - CheckerDock: running check now uses the standard RingLoadingIcon (warning ring), matching the app's loader instead of a blue spinner. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * ✨ feat(verify): auto-repair loop — re-run the agent with failure feedback on failed checks When required checks fail with onFail=auto_repair, automatically run a second iteration instead of ending at `failed`: - createRepairRunner: re-runs the SAME agent in the same topic with the failure feedback as the prompt, re-snapshots the plan onto the repair operation and confirms it so it re-verifies on completion (the next round). Capped at MAX_REPAIR_ROUNDS via parent-chain depth to prevent runaway loops. - maybeAutoRepair: fires only once every required check has a terminal result, so it works for inline LLM checks (triggered from lifecycle) and async agent checks (triggered from the verify tool's writeback path). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * ✨ feat(verify): open check result detail in portal & rename artifact→result - add a VerifyResult portal view: clicking any check row opens that result's detail (verdict, confidence, Toulmin sections, suggestion) on the right; agent checks expose their execution trace from inside the panel - CheckerDock rows are all clickable now (chevron affordance), status shown by icon only; verify card uses colorBgElevated - rename the run-result surface from "artifact" to "result" everywhere: RunArtifact → RunResult, phaseToArtifact → phaseToResult, and all `artifact.*` i18n keys → `result.*` - ship verify namespace zh-CN / en-US locales Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * ✨ feat(verify): enrich check result portal — criterion stepper, richer detail view Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * ✨ feat(verify): rubric run-policy config + repair feedback on the verify card Auto-repair feedback now lives on the failed round's role=verify message (content), and the VerifyMessageProcessor surfaces it into the repair run's context as a tagged user turn — so the repair op runs off history via a new execAgent `suppressUserMessage` path instead of injecting a synthetic user message. createVerifyMessage is awaited before verification to avoid a race. maxRepairRounds becomes a rubric-level config: new `verify_rubrics.config` jsonb column, read live at repair time via the plan's sourceRubricId. Adds a RubricConfig portal panel (reachable from the plan card's settings affordance) to view/edit it, wired through the verify store + TRPC. Verify domain types/vocab/config are extracted from the DB schema into @lobechat/types as the single source of truth; schema and consumers import from there. Tests: VerifyMessageProcessor dual behavior; VerifyRubricModel config round-trip; MessageModel.findVerifyMessageByOperationId. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * 🗃️ refactor(verify): squash the 3 verify migrations into one Collapse 0110 (tables) + 0111 (criteria.description) + 0112 (rubrics.config) into a single regenerated 0110_add_verify_tables so the PR ships one clean, idempotent migration. No schema change vs the three combined. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * ✨ feat(cli): verify rubric run-policy config commands + shrink judging-rule editor font CLI: `verify rubric create --max-repair-rounds`, `verify rubric view`, and `verify rubric update` exercise the rubric config endpoints end-to-end; adds a mocked command test. UI: judging-rule editor font 16px → 14px. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * ✨ feat(verify): editable rubric name in the config panel + default 3 repair rounds Add a name (title) field to the RubricConfig portal, persisted via a new updateRubricTitle store action + service (optimistic + debounced, alongside the config write-back). Bump DEFAULT_MAX_REPAIR_ROUNDS 2 → 3. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * ♻️ refactor(verify): extract generateVerifyPlan into installable lobe-delivery-checker tool Move the delivery-checker plan-creation flow out of the always-on lobe-agent tool into a new standalone, installable builtin tool `lobe-delivery-checker` (Skill Store, opt-in per agent — not loaded by default). lobe-agent no longer ships generateVerifyPlan. - new packages/builtin-tool-lobe-delivery-checker (manifest/types/systemRole + client Render/Inspector/Portal moved wholesale from lobe-agent) - new serverRuntimes/lobeDeliveryChecker.ts (generateVerifyPlan moved out of lobeAgent.ts), registered alongside verifyResult - registered installable in builtin-tools (no hidden/discoverable:false, not in defaultToolIds/alwaysOnToolIds/runtimeManagedToolIds); renders/inspectors/ portals/identifiers wired; lobe-agent portal entries removed - i18n keys moved builtins.lobe-agent.verifyPlan.* → builtins.lobe-delivery-checker.* Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * ✨ feat(agent): add `custom` tool mode; verify agent uses it instead of chat-mode Chat mode's contract is to strip ALL user/agent plugins (strict KB/memory/web allow-list) — so the verify sub-agent couldn't get its writeback tool without a leaky blanket rule. Introduce a third tool mode `custom` where the toolset is EXACTLY the agent's declared plugins (no always-on, no defaults, no activator), for focused builtin sub-agents. - chatConfig.toolMode: 'agent' | 'chat' | 'custom' (overrides enableAgentMode) - AgentToolsEngine: custom branch (defaultToolIds = plugins, rules = plugins-on, allowExplicitActivation only in agent mode); chatModeRules restored to strict - verify agent → toolMode: 'custom'; lobe-verify dropped from chatModeAllowedToolIds - test: custom mode enables exactly the declared plugin, no always-on / defaults Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
7989952d2e |
✨ feat(agent-signal): add CLI trigger command + golden snapshot fixture (#15360)
Part of LOBE-9434 (#1 / LOBE-9435). Adds the local verification tooling the execAgent migration depends on. - `lh agent-signal trigger`: enqueue any producer-side Agent Signal source event for the authenticated user (nightly_review / self_reflection / self_feedback_intent / user.message / tool.outcome.*) - server: `buildTriggerSourceEvent` default-payload builder + `AGENT_SIGNAL_TRIGGER_SOURCE_TYPES` allowlist, surfaced through a new authed `agentSignal.triggerSourceEvent` tRPC procedure that re-derives userId from context (owner-scoped, override can't repoint it) - golden snapshot fixture + dependency-free `assertGoldenFinalState` structural assertion (ideas/intents/writeOutcomes >= 1, brief non-empty) for use by the migration regression tests - builder unit tests + offline/live e2e, regenerated man page Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
2098ac8374 |
🐛 fix: remove the old cron job from lobehub (#14630)
* fix: remove the old cron job from lobehub * fix: add some ts back |
||
|
|
5fb795b092 |
✨ feat(cli): add lh hetero exec for standalone heterogeneous agent runs (#14431)
* 🌐 i18n: add taskDetail.runAll keys for subtask dependency runner Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * ✨ feat(cli): add `lh hetero exec` for standalone heterogeneous agent runs (LOBE-8523 phase 1a) Phase 1a of LOBE-8516: a Node-side `spawnAgent()` plus the CLI command that drives it. Standalone-only — no `--topic` / `--operation-id` / no server ingest. Output is `AgentStreamEvent` JSONL on stdout, one event per line. Why phase 1a is its own milestone: it lets us validate the producer pipeline end-to-end (`spawn → JsonlStreamProcessor → adapter → toStreamEvent`) under a plain Node process, get Device-mode + manual debugging unblocked, and ship without waiting on phase 2's server `heteroIngest` procedures. ## Shared `spawnAgent({ agentType, prompt, resumeSessionId, cwd, command })` - Lives in `@lobechat/heterogeneous-agents/spawn`. Pure Node — no Electron, no image cache, no on-disk tracing, no proxy env composition. Desktop main keeps its own bespoke spawn path for those host concerns; this minimal version is what the CLI sandbox + terminal use case needs. - CC: stream-json stdin format + the established preset flags. Codex: `exec` / `exec resume` form with `--json --skip-git-repo-check --full-auto`. - Returns `SpawnAgentHandle` with: async-iterable `events`, `exit` promise, `kill(signal)` (Unix process-group kill, Windows direct), `pid`, raw `stderr`. - Internally a single-queue async iterator coordinates between the stdout listeners and the consumer — keeps backpressure simple, no extra deps. ## `lh hetero exec` command ``` lh hetero exec --type claude-code|codex [--prompt - | --prompt <text>] # default stdin [--resume <sessionId>] [--cwd <path>] # default process.cwd() [--command <bin>] # default `claude` / `codex` [--operation-id <id>] # uuid v4 generated if omitted ``` - Reads prompt from stdin when omitted or `-`. - Forwards child stderr to ours so users see auth prompts / missing-binary errors. - Ctrl-C → SIGINT to the child's process group (Unix); a second Ctrl-C escalates to SIGKILL. - Exit code passthrough: child code 0/non-0 stays as-is; SIGINT / SIGTERM / SIGKILL map to POSIX 130 / 143 / 137. ## Out of scope (phase 1b — next PR) - `--topic` / `--operation-id` flags as REQUIRED + the BatchIngester - `--render none|jsonl` flag (phase 1a is implicit JSONL) - trpc `aiAgent.heteroIngest` / `heteroFinish` calls - Gateway WS interrupt subscription ## Validation - `bunx vitest run packages/heterogeneous-agents` — 113 passing (8 new spawnAgent tests + the 105 pre-existing on canary) - `bunx vitest run apps/cli/src/commands/hetero.test.ts` — 7 passing (all `--type` / `--prompt` / `--operation-id` / exit-code-passthrough / SIGINT-mapping branches) - Real end-to-end: `bun src/index.ts hetero exec --type claude-code --prompt 'Reply with exactly the word HELLO and nothing else.'` produced clean AgentStreamEvent JSONL (stream_start → 2 stream_chunks → step_complete turn_metadata → step_complete result_usage → stream_end → agent_runtime_end), every line stamped with the same auto-generated operationId. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * 🐛 fix(spawn): serialize pipeline pushes so flush waits for in-flight chunks When stdout emits multiple chunks back-to-back — or `'end'` lands while an earlier `pipeline.push()` is still awaiting the Codex tracker's filesystem reads — the per-chunk `.then` handlers ran concurrently. Two consequences: 1. Out-of-order events. Push #2's events could resolve before push #1's, so the JSONL stream came out shuffled. 2. Late-event loss. `'end'` would call `pipeline.flush()` and immediately set `streamEnded = true` while prior pushes were still pending. The async iterator could then return `{ done: true }` before those pushes queued their events. Fix: thread every `push()` / `flush()` / error-surface call through a single `pipelineQueue` `Promise` chain, the same shape the desktop controller uses for its broadcast queue. `flush()` now reliably runs after every queued push has drained, so `streamEnded` is the very last write. Two regression tests cover the failure modes by spying on `AgentStreamPipeline.push` to inject deterministic delays: - "preserves event ordering across async pipeline.push() calls" — chunk A resolves slower than chunk B; without the chain B arrives first. - "iterator drains slow in-flight pushes before flushing the stream" — `'end'` fires while a 40 ms push is still pending; without the chain the iterator returns done before the chunk's events queue. Bisected: both tests fail without the chain, pass with it. E2E re-smoke (`bun src/index.ts hetero exec --type claude-code` simple text + tool-using prompt + stdin) still produces clean ordered JSONL. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
c68dfa00df |
✨ feat(cli): add lh notify command for external agent callbacks (#13664)
* ✨ feat(cli): add `lh notify` command for external agent callbacks Add a new `lh notify` CLI command and server-side TRPC endpoint that allows external agents (e.g. Claude Code) to send callback messages to a topic and trigger the agent loop to process them. Fixes LOBE-6888 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * 🔧 chore(cli): replace sessionId with agentId and threadId in notify command Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> |
||
|
|
14cd81b624 |
✨ feat(cli): add migrate openclaw command (#13566)
* ✨ feat(cli): add `migrate openclaw` command for importing OpenClaw workspace Add a new CLI command `lh migrate openclaw` that imports all files from the OpenClaw workspace (~/.openclaw/workspace) as agent documents into the LobeHub inbox agent. Supports --source, --agent-id, --slug, --dry-run, and --yes options. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * ♻️ refactor(cli): restructure migrate as directory for future providers Refactor `migrate` command from a single file to a directory structure (`migrate/index.ts` + `migrate/openclaw.ts`) to support future migration sources like ChatGPT, Claude, Codex, etc. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * 🐛 fix(cli): remove unnecessary `as any` casts in migrate openclaw Use proper TrpcClient type instead of casting to any. Extract resolveInboxAgentId helper with correct typing. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * ♻️ refactor(cli): migrate openclaw creates a new "OpenClaw" agent by default Instead of importing into the inbox, the default behavior now creates a dedicated "OpenClaw" agent and imports workspace files as its documents. Use --agent-id to import into an existing agent instead. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * ✨ feat(cli): restore --agent-id and --slug options for migrate openclaw Support three modes: --agent-id (by ID), --slug (by slug, e.g. "inbox"), or default (create a new "OpenClaw" agent). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * ✨ feat(cli): print agent URL after migrate openclaw completes Show a clickable link (e.g. https://app.lobehub.com/agent/<id>) at the end of the import so users can open the agent directly. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * ✨ feat(cli): check login state early in migrate openclaw Verify authentication before scanning files so users get a clear "Run 'lh login' first" message upfront instead of after confirmation. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * ✨ feat(cli): read agent name, description, avatar from OpenClaw workspace Parse IDENTITY.md (or SOUL.md) for Name, Creature/Vibe/Description, and Emoji fields to populate the new agent's title, description, and avatar instead of hardcoding "OpenClaw". Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * 💄 style(cli): show emoji + name instead of agent ID in migrate output Display the agent avatar emoji and title throughout the migrate flow (confirmation, creation, importing). The agent ID only appears in the final URL. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * 🐛 fix(cli): exclude .venv from openclaw workspace scan Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * 🔧 chore(cli): expand excluded dirs/files for openclaw workspace scan Filter out IDE configs, VCS dirs, OS artifacts, dependency dirs, Python caches, build outputs, env files, and other common non-content items. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * update version * ✨ feat(cli): use `ignore` package for gitignore-based file filtering & improve output - Replace hardcoded EXCLUDED_NAMES set with `ignore` package (gitignore syntax) - Respect workspace .gitignore if present, plus comprehensive default rules - Cover all common languages/tools: Python, Ruby, Rust, Go, Java, .NET, etc. - Improve final output: friendlier completion message with agent name + URL Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * ✅ test(cli): add tests for migrate openclaw command Cover profile parsing, file filtering (gitignore + default rules), dry-run, agent resolution (--agent-id, --slug, default create), confirmation flow, error handling, and output formatting. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * ✨ feat(cli): filter placeholder emoji and binary/database files - Skip avatar values like (待定), _(待定)_, TBD, N/A, etc. - Add ignore rules for database files (*.sqlite, *.db, *.mdb, etc.), images, media, fonts, lock files, and compiled binaries - Runtime binary detection: check first 8KB for null bytes and skip binary files that slip through the extension filter - Add tests for placeholder emoji filtering, binary skip, and db exclusion Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * ✨ feat(api,cli): support optional createdAt for agent document upsert Thread an optional `createdAt` parameter through all layers: - Model: AgentDocumentModel.create/upsert accept optional createdAt, set both createdAt and updatedAt on documents + agent_documents rows - Service: UpsertDocumentParams includes createdAt - Router: agentDocument.upsertDocument accepts optional z.date() - CLI: migrate openclaw passes file mtime as createdAt to preserve original file timestamps Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * 💄 style(cli): add npx usage hint to auth error message Show 'npx -y @lobehub/cli login' alongside 'lh login' so users who haven't installed the CLI globally know how to authenticate. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * update version * ✨ feat(api,cli): support optional updatedAt for agent document upsert Add updatedAt alongside createdAt through all layers. When both are provided, updatedAt is used independently; when only createdAt is given, updatedAt falls back to createdAt. CLI now passes file birthtime as createdAt and mtime as updatedAt. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * 🐛 fix(cli): use os.homedir() for default source & wrap file reads in try - Replace process.env.HOME || '~' with os.homedir() so the default --source path resolves correctly on Windows and when HOME is unset - Move fs.readFileSync/statSync inside the try block so a single unreadable file doesn't abort the entire migration Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> |
||
|
|
dd7819b1be |
🔨 chore(cli): register task command and add kanban board view (#13511)
* ✨ feat(cli): register task command and add kanban board view Register the missing `registerTaskCommand` in program.ts so `lh task` commands are accessible. Add `--board` flag to `task list` that renders a kanban-style view grouping tasks by status columns (backlog, running, paused, completed, etc.) with color-coded borders. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * update --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> |
||
|
|
6e3053fcb3 | ✨ feat(cli): add generated man pages (#13200) |