Sync upstream qm through 866764e - #1
Merged
Merged
Conversation
…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
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.
Sync upstream qm
Merges upstream
yc-software/qmmaininto this private fork, bringing in the latest upstream features.Upstream range merged
5eb3393..866764e— 22 commitsNotable changes in this batch:
f15d7fc, model: custom provider endpoints yc-software/qm#200)fd890a8, credential broker: refuse percent-encoded parent traversal yc-software/qm#198)346388d)ea1fa61)e23b301)a7b5c8b)2fee4d7)--no-slack) needs no Slack pool slot (4b408da)Conflicts
None.
origin/maincarried zero private commits on core files(
git rev-list --count upstream/main..origin/main= 0), so this merge resolved as aclean fast-forward (
Updating 5eb3393..866764e, 76 files, +3681/-474).All organization-specific material lives under
deploy/layers/simbest/, which upstreamnever 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) — passnpm run lint(eslint .) — passnode cli/bin/qm.ts check --config deploy/layers/simbest/qm.config.jsonc— pass(private layer fully compatible with the new core; no layer changes required)
(custom-providers, custom-provider-route, provider-endpoints, credential-broker,
git-http-broker incl. the traversal-fix case, session-fork)
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.