Skip to content

Sync upstream qm through 866764e - #1

Merged
kyne0116 merged 22 commits into
mainfrom
sync-upstream-2026-08-06
Aug 6, 2026
Merged

Sync upstream qm through 866764e#1
kyne0116 merged 22 commits into
mainfrom
sync-upstream-2026-08-06

Conversation

@kyne0116

@kyne0116 kyne0116 commented Aug 6, 2026

Copy link
Copy Markdown
Member

Sync upstream qm

Merges upstream yc-software/qm main into this private fork, bringing in the latest upstream features.

Upstream range merged

5eb3393..866764e22 commits

Notable changes in this batch:

  • Custom model providers — admin-registered providers with base-URL overrides and admin-gated catalog (f15d7fc, model: custom provider endpoints yc-software/qm#200)
  • Credential broker: path-traversal fix — refuses percent-encoded parent traversal in brokered paths, a security hardening (fd890a8, credential broker: refuse percent-encoded parent traversal yc-software/qm#198)
  • Owner app shell — artifact-style top bar with a docked iterate chat (346388d)
  • web-ui polish — pre-fork origin badge, author-labeled bubbles, real sidebar links (ea1fa61)
  • self-API: spawn conversation — fork a fresh independent web session (e23b301)
  • Runtime defaults — persist full runtime defaults per scope (a7b5c8b)
  • No raw Slack ids — never show a raw Slack id where a person will read it (2fee4d7)
  • dev-instance — browser-only mode (--no-slack) needs no Slack pool slot (4b408da)

Conflicts

None. origin/main carried zero private commits on core files
(git rev-list --count upstream/main..origin/main = 0), so this merge resolved as a
clean fast-forward (Updating 5eb3393..866764e, 76 files, +3681/-474).

All organization-specific material lives under deploy/layers/simbest/, which upstream
never touches, so no path on either side could conflict. No file outside deploy/layers/
was edited by this fork.

Verification

  • npm install — ok (612 packages)
  • npm run typecheck (tsc --noEmit) — pass
  • npm run lint (eslint .) — pass
  • node cli/bin/qm.ts check --config deploy/layers/simbest/qm.config.jsoncpass
    (private layer fully compatible with the new core; no layer changes required)
  • Focused tests for new/changed code — 53/53 pass in 5.5s
    (custom-providers, custom-provider-route, provider-endpoints, credential-broker,
    git-http-broker incl. the traversal-fix case, session-fork)
  • Full root test suite — not run to completion in this environment (large suite; a
    fast-forward is upstream's CI-green code, so this is not a regression signal)

Organization layer

Nothing under deploy/layers/simbest/ was modified. The layer remains byte-identical;
only core advanced.

ReganBell and others added 22 commits August 4, 2026 09:16
…tered providers

Two layers of custom model endpoints:

1. ANTHROPIC_BASE_URL / OPENAI_BASE_URL / OPENROUTER_BASE_URL are parsed
   and validated once in config (bad URLs fail at boot), resolved through
   one provider-endpoints module, and applied uniformly: the pi harness,
   the claude/codex child harness process envs, and admin key validation
   (a gateway-issued key validates against the configured endpoint).

2. An org admin can register additional providers that speak the OpenAI
   or Anthropic wire protocol — base URL, API key, and the model ids to
   expose — via PUT /v1/admin/custom-providers/:slug. Registered models
   resolve through the same choke point as built-ins, surface in the
   catalog and model pickers, and serve on the pi and opencode harnesses.
   Keys validate against the registered endpoint (skippable for gateways
   without a models listing), live in the same encrypted store as the
   built-in provider keys, and are write-only. Built-in model ids and
   provider slugs are reserved. The admin portal gets a Custom providers
   card. DeepSeek / Kimi / xAI / a corporate gateway become request
   bodies, not code.

QA hardening: custom keys reach the model runtime for every provider,
models.json materialization is cached per registry version, a corrupt
custom key degrades only its provider, slashed custom ids win the
opencode modelRef lookup, model/name input caps, double-delete 404s,
and the picker refreshes when registrations change.

Addresses the custom-endpoint asks in yc-software#110, yc-software#60, yc-software#116, yc-software#104.
…e chat

Replace the injected edit bubble with a wrapper shell served to a
signed-in owner on top-level document loads: a slim top bar (app name,
version chip, update-reload pill) over the app in a same-origin iframe,
with a resizable docked chat column for iterating on the app. The
frame's own load carries sec-fetch-dest: iframe, so the app itself
proxies through byte-identical — no more HTML injection, no CSS/z-index
fights, and reloading a new version keeps the chat thread. Clients
without fetch metadata get the raw app (never a nested shell).

The shell wears the web-ui design system (neutral oklch grays, system
sans, dark mode via prefers-color-scheme) and picks up the org's
configured branding accent for the Chat toggle and update pill, falling
back to the web-ui default. Accent values are allowlist-sanitized
before landing in a style attribute.

With the chat panel open, the app frame auto-reloads the moment a new
version lands (the chat thread survives); with it closed, the polite
'Updated ↻ Reload' pill stays instead of yanking the page.
Keychain and connector views resolve Slack scope ids to real channel
and person names (the keychain route joins against the surface
directory), and every remaining spot that could print a raw scope id —
skills, crons, context labels — goes through one scopeTitle helper
with a human fallback instead.
…inks

A bundle of session UX polish:

- Forked sessions collapse their inherited history behind an origin
  badge — expand it to read the pre-fork transcript; the badge stays
  pinned at the top when every visible message is pre-fork. The fork
  origin (session + message index) is persisted with the session.
- Chat bubbles are labeled with the author's name whenever more than
  one person has spoken in the transcript, and every message gets its
  own hover-footer lane so actions never overlay the work header.
- Sidebar entries are real links: cmd/ctrl-click opens a session, cron,
  or view in a new tab; plain clicks route in-app as before.
- An untouched new chat drops out of the sidebar when you navigate
  away instead of littering it with empty sessions.
'Make default' saves the whole runtime selection — harness, model,
effort level, and fast mode — not just harness+model. The server
validates effort against the known thinking levels and only persists
fast mode for models that support it; the effective config echoes both
back so pickers seed correctly, and the default button appears whenever
any part of the selection differs from the scope default.
POST /v1/conversations creates a new conversation in a scope the caller
can act in — the same operation the web UI's New Chat performs, now
available to agents. The response carries the new conversation id and
scope; guardrails match the surface route (scope membership, audit).
dev up --no-slack boots a dev instance without leasing a Slack pool
slot: no Slack tokens required, the supervisor skips the Slack relay
processes, and doctor knows not to expect them. For working on the web
UI or core alone, an instance comes up with just a browser — Slack mode
is unchanged.
Admin grant changes, impersonation, and command-approval decisions are
intentionally excluded from the agent self-API. They read like
capability-parity gaps in an audit; they are walls. Write the reasoning
down in SECURITY.md so parity work routes around them, not through them.
The broker's path allowlist checked literal "../" but not its
percent-encoded forms, so an allowed prefix could be escaped with
%2e%2e%2f (single- or double-encoded). Decode before checking, reject
any path whose decoded form contains a parent traversal, and cover the
git smart-HTTP route with the same guard. Regression tests exercise
both routes with plain, single-, and double-encoded traversals.

Reported in yc-software#154 (closes the yc-software#125 git-broker finding).

Co-authored-by: yassine-ceo <yassine-ceo@users.noreply.github.com>
…ath-traversal

credential broker: refuse percent-encoded parent traversal
…lack-ids

Never show a raw Slack id where a person will read it
…roviders

model: custom provider endpoints
…olish

web-ui: fork origin badge, author-labeled bubbles, real sidebar links
…defaults

Persist full runtime defaults per scope
…nversation

self-API: spawn a fresh conversation
…lack

dev-instance: browser-only mode (--no-slack)
…nly-docs

docs(security): record the three deliberately portal-only actions
deploy: owner app shell with docked iterate chat
@kyne0116
kyne0116 merged commit 9a7a80b into main Aug 6, 2026
@kyne0116
kyne0116 deleted the sync-upstream-2026-08-06 branch August 6, 2026 01:33
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