Skip to content

feat(kiloclaw) "one click" install of a ClawByte into KiloClaw instance#3651

Open
St0rmz1 wants to merge 20 commits into
mainfrom
add-byte-install-route
Open

feat(kiloclaw) "one click" install of a ClawByte into KiloClaw instance#3651
St0rmz1 wants to merge 20 commits into
mainfrom
add-byte-install-route

Conversation

@St0rmz1
Copy link
Copy Markdown
Contributor

@St0rmz1 St0rmz1 commented Jun 1, 2026

Summary

Adds an install flow for ClawBytes so a user can go from a byte in the catalog to running it in their KiloClaw chat with one confirmation.

  • New route at claw/install/[source]/[slug] renders a confirmation card showing the byte title and description with Confirm Install and Cancel actions. The page fetches and verifies on the server, and dispatches only on an explicit Confirm click through a POST mutation, so loading the URL by itself never runs anything.
  • Byte content is fetched from a registry of approved sources (today byte maps to the catalog data.json). The host always comes from the registry rather than user input, the slug is percent encoded into one path segment, and redirects are not followed.
  • Payloads are signed with Ed25519. The app verifies the signature against a pinned public key, checks the key id, enforces an age window, and confirms the signed slug matches the requested slug. Only the signed fields (slug, title, description, prompt) are modelled, so unsigned content can never be shown.
  • Access is checked before any fetch. A user without an active subscription is routed to set one up, so fetching and verifying a byte happens only for paid users.
  • Dispatch is bound to the exact payload the user reviewed. The Confirm click sends the reviewed signature, the server fetches and verifies again, and refuses if the signature changed since the page rendered.
  • Each install opens its own conversation (a new forceNewConversation option on the shared message primitive) instead of appending to the user's most recent chat, and then navigates straight into that conversation.
  • The app reaches the chat service over an authenticated internal HTTP route (POST /internal/v1/post-message-as-user) guarded by a shared secret, because the app and the chat service run on different platforms and cannot use a service binding.
  • If the user has no instance yet, they are routed to provisioning. Install intent is not persisted across that flow, since the install link is repeatable.

Verification

Tested locally against a running app, catalog, chat service, and a provisioned instance.

  • Paid user with an instance: open the install link, click Confirm Install, land in a new conversation with the byte prompt sent and a reply.
  • Signature binding: a changed signature is refused with a clear message instead of dispatching.
  • No instance: routed to provisioning rather than dispatching.
  • Logged out: bounced to sign in, then returned to the confirmation page.
  • Unknown slug: shows a 404.
  • Loading the URL by itself never dispatches; the Confirm click is required.

Visual Changes

Before After
No install page existed Confirmation card: ClawByte badge with icon, byte title, the line "This ClawByte installs a skill to:" followed by the description, and Confirm Install and Cancel buttons

Reviewer Notes

Suggested focus areas:

  • Signature verification and the bound dispatch in apps/web/src/lib/kiloclaw/install.ts and install-dispatch.ts.
  • The access check that runs before any byte fetch in the install page.
  • The shared secret auth on the internal chat route.
  • The source registry as the only place a new origin can be added.

St0rmz1 added 18 commits May 28, 2026 15:14
…HTTP

  Lays the foundation for app.kilo.ai 1-click install of ClawBytes from
  kilo.ai. Not yet wired end-to-end (no tRPC mutation, no client
  dispatch, no cookie-through-provisioning); this stages the
  verification, registry, and per-user chat-injection HTTP surface.

  apps/web (cloud Next.js):
  - lib/kiloclaw/install-sources.ts: generalized source registry. v1
    ships a single `byte` source mapped to kilo.ai's data.json
    endpoint; KILO_AI_BASE_URL overrides for local dev. Adding a new
    source = appending one map entry.
  - lib/kiloclaw/install.ts: fetchInstallPayload(source, slug) does
    Zod parse + Ed25519 signature verify against pinned
    CLAWBYTE_SIGNING_PUBLIC_KEY. Rejects on missing key, kid
    mismatch, expired/future signedAt, version drift, or tampered
    envelope. Returns null on hard rejects so callers can't fall back
    to unsigned content.
  - lib/kiloclaw/install.test.ts: 11 cases covering happy ph,
    upstream 4xx/5xx, kid mismatch, tampered prompt/title, version
    drift, age window, unsigned payloads, and unconfigured pubkey.
  - app/(app)/claw/install/[source]/[slug]/page.tsx +
    InstallClient.tsx: server route fetches the payload behind the
    existing claw-layout auth gate; client shell renders a preview
    with title/description and a placeholder for the dispatch CTA.

  services/kilo-chat (Cloudflare Worker):
  - routes/internal.ts + auth-internal.ts: new /internal/v1/post-
    message-as-user HTTP endpoint, gated by an x-internal-api-key
    header verified against INTERNAL_API_SECRET. Thin wrapper around
    the existing postMessageAsUser RPC so non-Worker callers (the
    cloud Next.js app) can deliver an as-user chat turn without
    needing a service binding.
  - wrangler.jsonc: adds the INTERNAL_API_SECRET secrets-store
    binding (shared store, prod secret).
  - worker-configuration.d.ts: regenerated to include the new
    binding.
the plan wandered, bring it back inline with version 1 plan
Comment thread apps/web/src/lib/kiloclaw/install.ts Outdated
Comment thread apps/web/src/lib/kiloclaw/install-dispatch.ts Outdated
@kilo-code-bot
Copy link
Copy Markdown
Contributor

kilo-code-bot Bot commented Jun 1, 2026

Code Review Summary

Status: No Issues Found | Recommendation: Merge

Executive Summary

Incremental review of the latest commit (re-sync messageTextSchema into the kiloclaw plugin's local schemas copy) found no issues — both changed files are a comment reword and a structural refactor with no behavioral change. All previously flagged issues remain resolved.

Resolved Issues (from prior commits)
File Issue Status
apps/web/src/lib/kiloclaw/install.ts WARNING: Unbounded fallback bypassed MAX_RESPONSE_BYTES cap ✅ Fixed
apps/web/src/lib/kiloclaw/install-dispatch.ts SUGGESTION: autoCreateConversation: true was redundant alongside forceNewConversation: true ✅ Fixed
Files Reviewed (25 files)
  • apps/web/src/app/(app)/claw/install/[source]/[slug]/InstallClient.tsx
  • apps/web/src/app/(app)/claw/install/[source]/[slug]/page.tsx
  • apps/web/src/components/icons/KiloClawbsterIcon.tsx
  • apps/web/src/lib/kiloclaw/install-dispatch.test.ts — resolved
  • apps/web/src/lib/kiloclaw/install-dispatch.ts — resolved
  • apps/web/src/lib/kiloclaw/install-sources.test.ts
  • apps/web/src/lib/kiloclaw/install-sources.ts
  • apps/web/src/lib/kiloclaw/install.test.ts
  • apps/web/src/lib/kiloclaw/install.ts — resolved
  • apps/web/src/lib/kiloclaw/kilo-chat-internal-client.test.ts
  • apps/web/src/lib/kiloclaw/kilo-chat-internal-client.ts
  • apps/web/src/routers/kiloclaw-router.test.ts
  • apps/web/src/routers/kiloclaw-router.ts
  • packages/kilo-chat/src/index.ts
  • packages/kilo-chat/src/rpc-types.ts
  • packages/kilo-chat/src/schemas.ts — comment reword only
  • services/kilo-chat/src/__tests__/post-message-as-user.test.ts
  • services/kilo-chat/src/auth-internal.ts
  • services/kilo-chat/src/index.ts
  • services/kilo-chat/src/routes/internal.ts
  • services/kilo-chat/src/services/post-message-as-user.ts
  • services/kilo-chat/worker-configuration.d.ts
  • services/kilo-chat/wrangler.jsonc
  • services/kiloclaw/plugins/kilo-chat/src/synced/schemas.ts — re-sync: added messageTextSchema export, refactored textBlockSchema to use it

Reviewed by claude-sonnet-4.6 · 211,645 tokens

Review guidance: REVIEW.md from base branch main

@St0rmz1 St0rmz1 changed the title Add byte install route feat(kiloclaw) "one click" install of a ClawByte into KiloClaw instance Jun 1, 2026
});
}

const instance = await deps.getActiveInstance(userId);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Must-fix: This dispatch picks getActiveInstance(userId) after generic clawAccessProcedure access, but those are not bound to the same billing anchor. In an inconsistent state, such as a current subscription anchored to a destroyed or different row while an orphaned active row remains, the gate can pass and this sends a prompt into an unentitled runtime. Require access for this exact instance before resolving sandbox/chat, for example with requireKiloClawAccessAtInstance(userId, instance.id), or reuse the existing personal anchor resolution and fail closed on mismatch.

const url = `${getKiloChatBaseUrl()}/internal/v1/post-message-as-user`;
let res: Response;
try {
res = await fetch(url, {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested/nit: This secret-bearing fetch uses a base URL from NEXT_PUBLIC_KILO_CHAT_URL and follows redirects by default. A misconfigured or redirecting destination can send the internal API key and prompt outside kilo-chat. Prefer a server-only internal URL or allowlisted origin, and set redirect: 'error' before attaching the key.

<KiloClawbsterIcon className="h-4 w-auto" />
{sourceLabel}
</Badge>
<CardTitle className="mt-2 text-xl">{payload.title}</CardTitle>
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested/nit: payload.title and payload.description are external signed strings and can contain long unbroken tokens. On narrow screens, they can overflow this fixed-width card instead of reflowing. Add break-words or overflow-wrap:anywhere to both text blocks.

<Card className="w-full text-left">
<CardHeader>
<Badge variant="secondary" className="w-fit gap-1.5">
<KiloClawbsterIcon className="h-4 w-auto" />
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested/nit: This badge icon is decorative, but the SVG is exposed to assistive tech without a label. Mark this use aria-hidden="true" and focusable="false" by passing those props through KiloClawbsterIcon, or hide the SVG at this call site.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants