sync: develop → develop-auto (pre-promotion sync for skillsets #980)#981
Merged
Conversation
chore: promote /auto code-quality run → develop (restore CI parity)
Structural data for the landing lifecycle ring: the eight agent-facing stages in sequence (id, order, line icon, accent voice, CTA route), plus stroke line icons. Human copy lives in i18n and node geometry derives from `order`, so this stays a pure, decoupled data module.
Agent-first copy for the eight lifecycle stages (name / title / body / endpoint hint / CTA) plus the hub strings, under `landing.lifecycle`. EN and ZH mirror each other key-for-key.
Forge-style ring styling + motion: comet orbit, comet-synced travelling node pulse, activation ripple ping, flowing connector, counter-rotating inner ring. Re-pins the consumed tokens to a fixed dark context (the hero video is dark in BOTH themes) and adds a localized scrim for legibility. All motion is gated by prefers-reduced-motion.
Interactive ring overlaid on the looping hero video. Eight stages around a hairline circle; hover / focus / tap reveals a centre detail card (agent-API hint + CTA) and closes on leave / blur / tap-outside. The layer is pointer-events-none except the nodes and active card, so the video and the CTA plate stay interactive. Self-assembling entrance (ring draws, nodes stagger in, comet fades up). Mounted in HeroVideo. Colocated test covers the disclosure behaviour (open on hover/focus/tap, switch, tap-outside close).
Move the dynamic announcement popup from a centred, page-dimming modal to a non-blocking top-right notification card so it no longer covers the hero / lifecycle ring. Drops the full-screen backdrop (pointer-events-none wrapper + pointer-events-auto card); closes via the X button or Escape. Slides in from the right. Drops the now-inaccurate aria-modal.
Re-anchor the hardcoded launch popup to the top-right (non-blocking, no page-dimming backdrop) and compact it: tighter section spacing, smaller offer numerals, smaller padding, and a shorter max-height (82vh -> 64vh) so it no longer spans the viewport. Launch content is unchanged; overflow scrolls within the card.
Minor bump: landing skill-lifecycle ring + top-right non-blocking popups.
A collapsed top-right 'headline pill' that expands on hover/focus/tap into a dismissable stack. Aggregates the hardcoded launch announcement (pinned) with all dynamic announcements from usePublicAnnouncements(), newest first. Each item persists its dismissal in localStorage; the banner renders nothing once everything is dismissed. Solid card surfaces so it stays legible over the dark hero video and on light/dark app pages alike. Open-only on click so the emulated mouseenter on tap can't slam it shut on touch.
…949) Mount AnnouncementBanner in AnalyticsRoot so it appears on every page (not just the landing). Remove the centred AnnouncementPopup + LaunchCelebrationPopup from LandingPage and delete them — the banner subsumes both: the hardcoded launch content moves into the banner's condensed launch card, and dynamic announcements render as banner items. Net effect: one global, dismissable announcement surface instead of two landing-only modals.
…e-ring feat(web): skill-lifecycle orbital ring on the landing hero
Promotes 9 verified bug fixes from develop-auto: #632 ZIP-bomb caps · #757 CodeQL modulo-bias+ReDoS · #766 playground abort quota · #608 chat-completion tool_calls · #750 version-delete GUID routing · #751 notification dropdown refetch · #940 deleted-skill 404 · #941 My-Skills count refresh · #752 timestamp i18n. Cumulative suite green on the merged tree (ornn-api 1656 pass, ornn-web 337 pass, typechecks clean); each PR was CI-green at merge and acceptance criteria re-verified. #803/#805 (NyxID identity verification) intentionally NOT included — escalated to #948 for a human/NyxID trust-model decision.
…velop chore: promote verified bug-fix run (9 fixes) → develop
Dated release-notes file for the v0.11.0 develop → main promotion: 9 bug fixes + the #949 announcement-banner / skill-lifecycle-ring web features + the code-quality run (technical). Empty changeset so check-changeset passes on this docs-only PR. Co-authored-by: chronoai-shining <shiningwn@outlook.com>
release: promote develop → main (v0.11.0)
Co-authored-by: ornn-release-bot[bot] <283908294+ornn-release-bot[bot]@users.noreply.github.com>
chore: sync main → develop after v0.11.0
Add the type layer for the Ornn Assistant chatbot's data path. The assistant reuses the Playground SSE transport but speaks its own event contract, so the strings are deliberately distinct (`chat_text_delta` vs the Playground's `text-delta`) to keep the two streams from ever cross-wiring. The event union is a Zod discriminated union over chat_start / chat_text_delta / chat_error / chat_finish / keepalive — no `as any`. `parseAssistantEvent` validates each parsed SSE payload and returns null for anything malformed / off-contract, so a drifting backend surfaces as a dropped event rather than an untyped object reaching React. `usage` on chat_finish is forward-compatible: known fields optional, unknown keys stripped. `AssistantMessage` is a structural subset of `PlaygroundMessage` (id/role/content) so the existing ChatMessage renderer can paint assistant turns without adaptation.
Zustand store for the assistant conversation, streaming state, the live answer buffer, and the slide-in panel's open/closed state. Session-only (NO persist) exactly like playgroundStore — the assistant is a transient helper, not a saved thread, so conversation + panel state reset on reload. clearMessages resets the conversation but intentionally leaves panel visibility untouched (clearing a chat shouldn't close the panel the user is looking at). finalizeAssistantMessage is a no-op on an empty buffer so an aborted-before-first-token turn doesn't push a blank bubble.
POST /api/v1/assistant/chat streaming client, mirroring playgroundStreamApi: canonical Bearer auth via the auth store's ensureFreshToken, AbortController cancel, shared parseSseChunk. Every parsed data: payload is validated through parseAssistantEvent before dispatch; unrecognized events are debug-logged and skipped rather than forwarded untyped. Non-OK responses are decoded as RFC 7807 problem+json (per docs/CONVENTIONS.md) — detail/code are lifted into a synthetic chat_error event so the UI has one uniform failure channel regardless of whether the failure was transport-level or a streamed error.
Drives the send → stream → display loop. Reuses the playground typewriter pacer: chat_text_delta events fill a pending buffer that a fixed-cadence pacer drains char-by-char, with adaptive catch-up so the visible text stays within ~1s of what's been received. Terminal events (chat_finish / chat_error / abort) flush the buffer immediately. Errors surface on BOTH the store (inline panel display) and a toast, so a user who scrolled away still sees the failure. keepalive heartbeats are ignored. The in-flight stream and pacer interval are torn down on unmount.
Mirror the backend LlmProviderModel contract (ornn-api settings llmProviders/types.ts) on the web client: add enabledForAssistant + defaultForAssistant to LlmProviderModel, and the matching optional fields to ModelFlagsPatchInput so the admin drawer can PATCH the assistant surface. The backend PATCH schema already accepts both as optional booleans and the repository defaults them to false, so existing provider docs deserialize unchanged.
Add a fourth surface column (Assistant) to the per-provider model manager, mirroring Playground and Skill-Gen: an enable Toggle plus a default Radio per model row, each firing the existing optimistic PATCH with enabledForAssistant / defaultForAssistant. Switched the table off table-fixed so four columns share width gracefully, and bumped the archived-section colSpan to 4. Column header copy stays inline English to match its hardcoded Playground / Skill-Gen siblings in this admin-only drawer; the user-facing assistant strings are fully i18n'd in the widget.
The per-provider row summary counted playground- and skillGen-enabled
models; add the assistant-enabled count so admins can see at a glance
how many models back the Ornn Assistant surface. Extends the
modelCounts i18n string (en + zh) with the new {{assistant}} slot.
… section (#970) Adds the third LLM surface (after playground/skillGen) that backs the Ornn Assistant repo-aware Q&A chatbot. - LlmProviderModel gains per-surface flags enabledForAssistant / defaultForAssistant; SurfaceKey gains 'Assistant'. Reads of pre-#970 provider docs default both to false so an existing model never auto-routes to the assistant until an admin opts it in. - Surface union widened to 'playground'|'skillGen'|'assistant'. The surface-field helpers and the at-most-one-default-per-surface invariant are now table-driven (SURFACE_KEY + ALL_SURFACES) so a future surface is a one-line addition, not scattered ternaries. - New 'assistant' settings section (defaultProviderId, defaultModelId, sseKeepAliveMs, defaultMonthlyQuota) mirroring playground/skillGen, registered in the section registry and exposed via SettingsService.getAssistant(). Surfaces stay independent: flipping the assistant default leaves the playground/skillGen defaults untouched (covered by new unit tests for resolver precedence + the cross-provider single-default invariant).
…rs (#970) - GET /me/models accepts surface=assistant; resolution-error labels are table-driven so the assistant surface emits a coherent MODEL_UNAVAILABLE message instead of borrowing skill-generation's. - bootstrap resolveLlmProviderForSurface / resolveSurfaceDefaults now resolve the 'assistant' surface from the new settings section. The shared NyxLlmClient is reused unchanged.
…#970) Carried over from the ProviderEditDrawer test template; the models-drawer test never references ReactNode. Removing it clears the @typescript-eslint/no-unused-vars lint error.
A floating bottom-right launcher opens a corner chat panel that streams
repo-aware answers about Ornn. Reuses the Playground chat primitives
(ChatMessage / ChatInput) and the assistant data layer (useAssistantChat
+ useAssistantStore).
Forge Workshop language (docs/DESIGN.md): semantic tokens only,
letterpress impression shadows (cta-letterpress / card-impression),
press-DOWN launcher hover, Framer Motion panel reveal that collapses
under prefers-reduced-motion. a11y: ESC + backdrop close, focus moves
into the composer on open and returns to the launcher on close,
focus-visible ember rings, labelled controls, a status-role thinking
indicator. Empty state suggests the three example questions
("What is Ornn?", "How is Ornn different?", "Find a skill that does X");
clicking one fills the composer for editing before send.
All strings via react-i18next (en + zh). Session-scoped — no persistence.
Render <AssistantWidget /> from RootLayout, gated on useIsAuthenticated so it only exists for signed-in users. The widget portals its own launcher + panel to document.body, so mounting it here (rather than inside <main>, which is overflow-hidden) keeps it clear of the layout clip and above page content while staying below toasts.
…oader (#970) Introduces the reusable KB primitives that ground the Ornn Assistant's answers in the repo's own knowledge: - tokens.ts: deterministic chars/4 token estimate + budget clamp + ASSISTANT_KB_TOKEN_BUDGET env resolution (default 18k). Model-agnostic on purpose — an exact tokenizer for one model is meaningless for another; determinism beats precision here. - distiller.ts: the KbDistiller contract + DeterministicKbDistiller v1 (markdown section extraction → per-source cap → titled concat → global budget clamp). The interface is the documented hook for a future LlmKbDistiller ("big model reads the repo at build time") — same contract, model-driven summarization swapped in underneath. - loader.ts: cached, fail-soft runtime loader that reads the committed digest artifact, strips its provenance header, and defensively clamps to budget. A failed read degrades to empty grounding, never a crash. - sources.ts: priority-ordered, capped source manifest (docs only, never code/secrets) consumed at build time. Pure + deterministic by construction (no clocks/RNG/network). Unit tests cover budget enforcement, section extraction, caching, and fail-soft.
Adds the build pipeline that turns the curated source manifest into the runtime grounding artifact: - scripts/build-assistant-kb.ts reads the manifest docs from the repo root, runs the DeterministicKbDistiller, and writes the committed digest.generated.md. Output carries an HTML-comment provenance header (stripped by the loader, never fed to the model) and NO timestamp, so re-running on unchanged inputs is byte-identical (clean diffs, stable CI). Missing source docs are skipped with a warning, not a failure. - digest.generated.md: the committed artifact (~12.4k tokens, 6 sources) — README overview, CLAUDE positioning, ARCHITECTURE, the HTTP agent manual, API conventions, and a design overview slice. - digest.artifact.test.ts guards the shipped artifact: non-empty, within budget, header hidden, Ornn-grounded, no secret-shaped content. - package.json: `bun run build:assistant-kb` to regenerate. Regenerate the artifact whenever the manifest or source docs change.
#970) The Ornn Assistant is a billed LLM surface, so quota must reserve + charge against it. Widen the quota `Surface` type to include 'assistant' and resolve its default allotment from `assistant.defaultMonthlyQuota`. Kept surgical to avoid frontend/admin ripple: - `SURFACES` (admin-grant + redemption-code enums, quota-snapshot UI) stays ["playground","skillGen"]. The assistant isn't admin-grantable or redeemable in v1 — its allotment is the section default only. - Introduced `GrantableSurface = SURFACES[number]`; grant/bulkGrant + the redemption grant entry use it, so they stay assignable to the notification layer's narrow surface type. The QuotaSnapshot shape is unchanged. - `QuotaDefaults.defaultAssistantMonthly` is optional so existing resolver mocks keep compiling; the production resolver supplies it. Absent → 0 allotment (fail-closed) until wired.
…ce (#970) The pure (HTTP-free) core of the Ornn Assistant — a non-agentic Q&A pipeline: - retrieval.ts: ScopedSkillRetriever runs visibility-scoped keyword search (scope "mixed") and enforces canReadSkill again at the projection layer, emitting RetrievedSkill — a SAFE projection (name, description, tags, category, createdOn, createdBy person-id). Listing fields explicitly (never spreading the doc) is the data-safety boundary: no createdByEmail/DisplayName, storageKey, skillHash, sharedWith* or any PII can leak. - contextAssembler.ts: ASSISTANT_SYSTEM_PROMPT (grounded-Q&A persona, no fabrication, no cross-user data) + curated KB + scoped skills, all in one leading developer message; conversation turns follow verbatim. - chatService.ts: emits chat_start → text deltas → chat_finish for ONE streamed completion with NO tools (the structural guarantee that the assistant can never trigger an execution loop). Retrieval failure is non-fatal (KB-only still answers); stream error → chat_error with a catalog code; best-effort usage on finish. Tests pin the data-safety projection (incl. a projection-layer drop of an unreadable skill), the no-tools guarantee, fail-soft retrieval, and the event sequence.
Mounts the assistant endpoint following the playground reference + CONVENTIONS pipeline: nyxidAuth → rateLimit(30/min) → validateBody → resolveModel(assistant) → buildActorContext → quota reserve(assistant) → SSE stream → chargeOnCompletion. Model resolution + quota reserve run before the stream so a misconfig/cap-hit returns clean RFC 7807 JSON; in-stream failures surface as a chat_error event. SSE frames carry both the native `event:` line and a JSON `data:` line whose type matches (CONVENTIONS §6.3): chat_start / chat_text_delta / chat_error / chat_finish + keepalive comment frames. Quota is reconciled in a finally that always runs (commit on success / billed-then-aborted, release on pre-token system error). bootstrap wires wireAssistant over the shared NyxLlmClient + SkillRepo + QuotaService, resolving the assistant surface defaults + sseKeepAliveMs from settings, and warms the KB cache at boot. Route mounted under /api/v1. Integration tests cover framing, auth/validation/model/quota gates, and — mandatory — that a private skill + PII never reach the streamed context through the real pipeline.
Adds assistantChatRequestBodySchema + assistantChatEventSchema and the `POST /api/v1/assistant/chat` path (SSE response, 400/401/429/503 error responses, "Assistant" tag) so the generated openapi.json documents the new endpoint and its event contract.
Single minor changeset (fixed-versioned ornn-api + ornn-web) describing the Ornn Assistant feature. Required by the check-changeset CI gate.
The Ornn Assistant endpoint POST /v1/assistant/chat ships without any new
contract surface to document, but its SSE event set and error behaviour
were undocumented. Two precise additions:
- CONVENTIONS.md §6.2: add the assistant row to the SSE endpoint mapping
table — events chat_start, chat_text_delta, chat_error, chat_finish
(the tool/file events from playground/chat do not apply here).
- ERRORS.md: note under upstream_unavailable that /assistant/chat
introduces NO new error codes. Pre-stream failures reuse
validation_error (400), authentication_required (401), rate_limited
(429), and MODEL_NOT_ENABLED / MODEL_NOT_FOUND (400, only when an
explicit modelId is supplied). In-stream LLM failure surfaces as an
SSE chat_error event with code upstream_unavailable and no chat_finish.
Verified against ornn-api/src/domains/assistant/{routes,chatService}.ts
and the shared throwModelResolutionError helper.
Quality-review nit: the frontend assistantUsageSchema used promptTokens/completionTokens while the backend AssistantUsage contract (ornn-api domains/assistant/types.ts) and the OpenAPI spec emit inputTokens/outputTokens/totalTokens. Rename the two drifted fields so a populated chat_finish.usage validates instead of being silently stripped. totalTokens was already correct.
Two quality-review a11y nits on the assistant panel: - The panel declares role=dialog / aria-modal=true but had no focus trap, so Tab / Shift+Tab escaped to the backdrop'd page behind it. Add a trap scoped to the dialog via onKeyDown (currentTarget — no ref threading) that wraps focus at the first/last focusable. The existing focus-move-into-composer-on-open and restore-to-launcher-on-close behavior is unchanged. - The header close / clear IconButtons were h-8 w-8 (32px), under the 44px mobile touch-target guideline in docs/DESIGN.md. Bump the hit area to h-11 w-11 (44px); the launcher and suggestion rows already met it.
Security review finding #1 (LOW): the architecture + conventions KB sources distilled whole docs, so internal infra surfaced in the digest any authenticated user can elicit — env-var catalogs, internal proxy header names (X-NyxID-*/X-Ornn-Caller-*), telemetry/observability internals, and the user-directory section. No secret VALUES, but needless internal-recon surface. Add a `headings` allow-list to both sources (mirroring README/DESIGN): - architecture → Project Overview, External Services, Skill Format only (drops the PostHog/telemetry, caller-type-detection, env-var config, and user-directory sections). - conventions → §1 Response/error, §2 URL structure, §3 HTTP semantics, §4 Query params, §6 SSE only (drops §5 Auth's internal X-NyxID-* note and §7–§12 deprecation/caching/observability/architecture internals). Regenerated digest is clean of every internal-recon marker (grep ✓) and still carries the user-relevant grounding; ~10.8k tokens, deterministic.
The grounding digest (digest.generated.md) is a committed build artifact distilled from the repo docs. If a source doc or the source manifest changes but the digest isn't regenerated, the assistant silently ships stale grounding. Add an `assistant-kb-freshness` CI job that rebuilds the digest with `bun run build:assistant-kb` and `git diff --exit-code`s the artifact — the build is deterministic (no timestamp/RNG), so a clean tree means the committed digest is in sync. A stale digest fails the PR with a clear "re-run build:assistant-kb and commit" annotation.
Quality review flagged that the SSE client had only indirect coverage
via the hook. Add a focused transport test that mocks fetch + the auth
store and exercises the four load-bearing behaviors:
- POSTs to /api/v1/assistant/chat with the Bearer header and the
{messages, modelId} JSON body
- parses chat_* frames off the stream via the real sseParser, in order
(incl. a frame split across two chunk boundaries → remainder buffering)
- maps a non-OK RFC 7807 problem+json response to a synthetic
chat_error carrying detail + code
- wires the AbortController so handle.abort() cancels the request and
the AbortError path stays silent (no spurious chat_error)
The sseParser and event schema are the real modules, so this also guards
the parse → validate → dispatch seam end to end.
…hatbot [Feature] Ornn Assistant — repo-aware Q&A chatbot (API + web widget)
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.
Routine sync of
developintodevelop-autoso the skillsets promotion PR #980 (develop-auto → develop) becomes up-to-date and mergeable under develop's strict status-check rule. Brings develop-only work (Ornn Assistant #970/#975) into develop-auto. No conflicts (develop-auto↔develop is MERGEABLE). Merge as a MERGE COMMIT (not squash).