Compare commits

..
Author SHA1 Message Date
james.maesandClaude Sonnet 5.5 3539f5fd93 fix(assistant): store the transcript before message.end so the panel shows the answer
QRun-IO/ci-policy: ci / fork-guard (pull_request) Skipped
QRun-IO/ci-policy: ci / head-profile (pull_request) Skipped
QRun-IO/ci-policy: ci / application-template-0 (pull_request) Skipped
QRun-IO/ci-policy: ci / gradle-1 (pull_request) Skipped
QRun-IO/ci-policy: ci / java-docker-4 (pull_request) Skipped
QRun-IO/ci-policy: ci / java-frontend-browser-5 (pull_request) Skipped
QRun-IO/ci-policy: ci / java-maven-6 (pull_request) Skipped
QRun-IO/ci-policy: ci / node-8 (pull_request) Skipped
QRun-IO/ci-policy: ci / java-maven-7 (pull_request) Skipped
QRun-IO/ci-policy: ci / node-10 (pull_request) Skipped
QRun-IO/ci-policy: ci / source-config-13 (pull_request) Skipped
QRun-IO/ci-policy: ci / android-0 (pull_request) Skipped
QRun-IO/ci-policy: ci / original-business (pull_request) Skipped
QRun-IO/ci-policy: ci / original-mcp (pull_request) Skipped
QRun-IO/ci-policy: ci / original-voyage (pull_request) Skipped
QRun-IO/ci-policy: ci / original-cli (pull_request) Skipped
QRun-IO/ci-policy: ci / not-enrolled (pull_request) Skipped
QRun-IO/ci-policy: native / fork-deny (pull_request) Skipped
QRun-IO/ci-policy: native / dotnet-0 (pull_request) Skipped
Shared Java Maven CI with Docker / secrets (push) Successful in 32s
QRun-IO/ci-policy: native / native-result (pull_request) Successful in 11s
QRun-IO/ci-policy: ci / repository-policy (pull_request) Skipped
QRun-IO/ci-policy: ci / java-core-2-head (pull_request) Skipped
QRun-IO/ci-policy: ci / java-docker-3-head (pull_request) Skipped
QRun-IO/ci-policy: ci / java-maven-6-head (pull_request) Skipped
QRun-IO/ci-policy: ci / java-maven-7-head (pull_request) Skipped
Shared Java Maven CI with Docker / verify (push) Successful in 7m47s
Shared Java Maven CI with Docker / gate (push) Successful in 27s
qrun-release / release (push) Successful in 12m20s
qrun-release / postpublish (push) Skipped
QRun-IO/ci-policy: Shared Java Maven CI with Docker / gate (pull_request) Successful in 20s
QRun-IO/ci-policy: ci / java-docker-3 (pull_request) Successful in 14m14s
QRun-IO/ci-policy: ci / ci-result (pull_request) Successful in 8s
Release candidate / release-rc (pull_request) Skipped
QRun-IO/ci-policy: ci / node-9 (pull_request) Skipped
QRun-IO/ci-policy: ci / node-playwright-11 (pull_request) Skipped
QRun-IO/ci-policy: ci / repository-policy-12 (pull_request) Skipped
QRun-IO/ci-policy: ci / source-config-14 (pull_request) Skipped
QRun-IO/ci-policy: Shared Java Maven CI with Docker / secrets (pull_request) Successful in 31s
QRun-IO/ci-policy: Shared Java Maven CI with Docker / verify (pull_request) Successful in 6m48s
The panel reloads the conversation the moment the stream ends. The
redacted transcript (message parts, pending actions, stored events) was
flushed only after message.end was sent, so that reload read the "[]"
placeholders and showed the newest question and answer as empty bubbles
until the panel was closed and reopened. Persist and flush first, then
emit message.end.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-04 19:16:11 -06:00
forge-recovery 2d6df82f16 ci: enroll shared Gitea lifecycle
QRun-IO/ci-policy: ci / fork-guard (push) Skipped
QRun-IO/ci-policy: ci / head-profile (push) Skipped
QRun-IO/ci-policy: ci / application-template-0 (push) Skipped
QRun-IO/ci-policy: ci / gradle-1 (push) Skipped
QRun-IO/ci-policy: ci / java-docker-4 (push) Skipped
QRun-IO/ci-policy: ci / java-frontend-browser-5 (push) Skipped
QRun-IO/ci-policy: ci / java-maven-6 (push) Skipped
QRun-IO/ci-policy: ci / java-maven-7 (push) Skipped
QRun-IO/ci-policy: ci / node-8 (push) Skipped
QRun-IO/ci-policy: ci / node-9 (push) Skipped
QRun-IO/ci-policy: ci / node-10 (push) Skipped
QRun-IO/ci-policy: ci / node-playwright-11 (push) Skipped
QRun-IO/ci-policy: ci / repository-policy-12 (push) Skipped
QRun-IO/ci-policy: ci / source-config-13 (push) Skipped
QRun-IO/ci-policy: ci / source-config-14 (push) Skipped
QRun-IO/ci-policy: native / fork-deny (push) Skipped
QRun-IO/ci-policy: native / dotnet-0 (push) Skipped
QRun-IO/ci-policy: native / native-result (push) Successful in 8s
Shared Java Maven CI with Docker / secrets (push) Successful in 31s
QRun-IO/ci-policy: Shared Java Maven CI with Docker / secrets (push) Successful in 31s
QRun-IO/ci-policy: ci / repository-policy (push) Skipped
QRun-IO/ci-policy: ci / java-core-2-head (push) Skipped
QRun-IO/ci-policy: ci / java-maven-6-head (push) Skipped
QRun-IO/ci-policy: ci / java-maven-7-head (push) Skipped
Shared Java Maven CI with Docker / verify (push) Successful in 8m28s
QRun-IO/ci-policy: Shared Java Maven CI with Docker / gate (push) Successful in 16s
QRun-IO/ci-policy: ci / java-docker-3 (push) Successful in 10m38s
QRun-IO/ci-policy: ci / ci-result (push) Successful in 8s
Shared Java Maven CI with Docker / gate (push) Successful in 17s
qrun-release / release (push) Successful in 10m53s
Shared QRun source version / version (push) Failing after 12m39s
qrun-release / postpublish (push) Failing after 12m39s
QRun-IO/ci-policy: ci / android-0 (push) Skipped
QRun-IO/ci-policy: ci / original-mcp (push) Skipped
QRun-IO/ci-policy: ci / not-enrolled (push) Skipped
QRun-IO/ci-policy: ci / java-docker-3-head (push) Skipped
QRun-IO/ci-policy: Shared Java Maven CI with Docker / verify (push) Successful in 7m31s
2026-11-02 22:09:34 +00:00
forge-recovery db8f5398b8 ci: enroll shared Gitea lifecycle
QRun-IO/ci-policy: ci / fork-guard (push) Skipped
QRun-IO/ci-policy: ci / head-profile (push) Skipped
QRun-IO/ci-policy: ci / application-template-0 (push) Skipped
QRun-IO/ci-policy: ci / gradle-1 (push) Skipped
QRun-IO/ci-policy: ci / java-docker-4 (push) Skipped
QRun-IO/ci-policy: ci / java-frontend-browser-5 (push) Skipped
QRun-IO/ci-policy: ci / java-maven-6 (push) Skipped
QRun-IO/ci-policy: ci / java-maven-7 (push) Skipped
QRun-IO/ci-policy: ci / node-8 (push) Skipped
QRun-IO/ci-policy: ci / node-9 (push) Skipped
QRun-IO/ci-policy: ci / node-10 (push) Skipped
QRun-IO/ci-policy: ci / node-playwright-11 (push) Skipped
QRun-IO/ci-policy: ci / repository-policy-12 (push) Skipped
QRun-IO/ci-policy: ci / source-config-13 (push) Skipped
QRun-IO/ci-policy: ci / source-config-14 (push) Skipped
QRun-IO/ci-policy: native / fork-deny (push) Skipped
QRun-IO/ci-policy: native / dotnet-0 (push) Skipped
QRun-IO/ci-policy: native / native-result (push) Successful in 7s
QRun-IO/ci-policy: Shared Java Maven CI with Docker / secrets (push) Successful in 26s
QRun-IO/ci-policy: ci / repository-policy (push) Skipped
QRun-IO/ci-policy: ci / java-core-2-head (push) Skipped
QRun-IO/ci-policy: ci / java-maven-6-head (push) Skipped
QRun-IO/ci-policy: ci / java-maven-7-head (push) Skipped
QRun-IO/ci-policy: ci / java-docker-3 (push) Successful in 15m47s
QRun-IO/ci-policy: ci / ci-result (push) Successful in 8s
QRun-IO/ci-policy: ci / android-0 (push) Skipped
QRun-IO/ci-policy: ci / original-mcp (push) Skipped
QRun-IO/ci-policy: ci / not-enrolled (push) Skipped
QRun-IO/ci-policy: ci / java-docker-3-head (push) Skipped
QRun-IO/ci-policy: Shared Java Maven CI with Docker / verify (push) Successful in 14m49s
QRun-IO/ci-policy: Shared Java Maven CI with Docker / gate (push) Successful in 17s
2026-11-02 21:30:15 +00:00
forge-recoveryandCodex f51482f69d ci: enroll shared Gitea release-candidate lifecycle
QRun-IO/ci-policy: ci / fork-guard (pull_request) Skipped
QRun-IO/ci-policy: ci / head-profile (pull_request) Skipped
QRun-IO/ci-policy: ci / application-template-0 (pull_request) Skipped
QRun-IO/ci-policy: ci / gradle-1 (pull_request) Skipped
QRun-IO/ci-policy: ci / java-docker-4 (pull_request) Skipped
QRun-IO/ci-policy: ci / java-frontend-browser-5 (pull_request) Skipped
QRun-IO/ci-policy: ci / java-maven-6 (pull_request) Skipped
QRun-IO/ci-policy: ci / java-maven-7 (pull_request) Skipped
QRun-IO/ci-policy: ci / node-8 (pull_request) Skipped
QRun-IO/ci-policy: ci / node-10 (pull_request) Skipped
QRun-IO/ci-policy: ci / source-config-13 (pull_request) Skipped
QRun-IO/ci-policy: ci / android-0 (pull_request) Skipped
QRun-IO/ci-policy: ci / not-enrolled (pull_request) Skipped
QRun-IO/ci-policy: native / fork-deny (pull_request) Skipped
QRun-IO/ci-policy: native / dotnet-0 (pull_request) Skipped
QRun-IO/ci-policy: native / native-result (pull_request) Successful in 6s
QRun-IO/ci-policy: ci / repository-policy (pull_request) Skipped
QRun-IO/ci-policy: ci / java-core-2-head (pull_request) Skipped
QRun-IO/ci-policy: ci / java-docker-3-head (pull_request) Skipped
QRun-IO/ci-policy: ci / java-maven-6-head (pull_request) Skipped
QRun-IO/ci-policy: ci / java-maven-7-head (pull_request) Skipped
QRun-IO/ci-policy: Shared Java Maven CI with Docker / gate (pull_request) Successful in 15s
QRun-IO/ci-policy: ci / java-docker-3 (pull_request) Successful in 59m43s
QRun-IO/ci-policy: ci / ci-result (pull_request) Successful in 6s
Release candidate / release-rc (pull_request) Skipped
QRun-IO/ci-policy: ci / node-9 (pull_request) Skipped
QRun-IO/ci-policy: ci / node-playwright-11 (pull_request) Skipped
QRun-IO/ci-policy: ci / repository-policy-12 (pull_request) Skipped
QRun-IO/ci-policy: ci / source-config-14 (pull_request) Skipped
QRun-IO/ci-policy: Shared Java Maven CI with Docker / secrets (pull_request) Successful in 13s
QRun-IO/ci-policy: Shared Java Maven CI with Docker / verify (pull_request) Successful in 6m42s
Co-Authored-By: Codex <noreply@openai.com>
2026-11-02 20:13:19 +00:00
4083e5041f ci: add the isolated postpublish job to the QRun release caller
QRun-IO/ci-policy: ci / fork-guard (pull_request) Skipped
QRun-IO/ci-policy: ci / head-profile (pull_request) Skipped
QRun-IO/ci-policy: ci / application-template-0 (pull_request) Skipped
QRun-IO/ci-policy: ci / gradle-1 (pull_request) Skipped
QRun-IO/ci-policy: ci / java-docker-4 (pull_request) Skipped
QRun-IO/ci-policy: ci / java-frontend-browser-5 (pull_request) Skipped
QRun-IO/ci-policy: ci / java-maven-6 (pull_request) Skipped
QRun-IO/ci-policy: ci / java-maven-7 (pull_request) Skipped
QRun-IO/ci-policy: ci / node-8 (pull_request) Skipped
QRun-IO/ci-policy: ci / node-10 (pull_request) Skipped
QRun-IO/ci-policy: ci / source-config-13 (pull_request) Skipped
QRun-IO/ci-policy: ci / not-enrolled (pull_request) Skipped
QRun-IO/ci-policy: native / fork-deny (pull_request) Skipped
Shared Java Maven CI with Docker / secrets (push) Successful in 16s
QRun-IO/ci-policy: native / dotnet-0 (pull_request) Skipped
QRun-IO/ci-policy: ci / repository-policy (pull_request) Skipped
QRun-IO/ci-policy: ci / java-core-2-head (pull_request) Skipped
QRun-IO/ci-policy: ci / java-docker-3-head (pull_request) Skipped
QRun-IO/ci-policy: ci / java-maven-6-head (pull_request) Skipped
QRun-IO/ci-policy: ci / java-maven-7-head (pull_request) Skipped
QRun-IO/ci-policy: native / native-result (pull_request) Successful in 5s
Shared Java Maven CI with Docker / verify (push) Successful in 6m11s
Shared Java Maven CI with Docker / gate (push) Successful in 13s
qrun-release / release (push) Successful in 2h16m43s
qrun-release / postpublish (push) Skipped
QRun-IO/ci-policy: Shared Java Maven CI with Docker / gate (pull_request) Successful in 9s
QRun-IO/ci-policy: ci / java-docker-3 (pull_request) Successful in 2h23m0s
QRun-IO/ci-policy: ci / ci-result (pull_request) Successful in 4s
Release candidate / release-rc (pull_request) Skipped
QRun-IO/ci-policy: ci / node-9 (pull_request) Skipped
QRun-IO/ci-policy: ci / node-playwright-11 (pull_request) Skipped
QRun-IO/ci-policy: ci / repository-policy-12 (pull_request) Skipped
QRun-IO/ci-policy: ci / source-config-14 (pull_request) Skipped
QRun-IO/ci-policy: Shared Java Maven CI with Docker / secrets (pull_request) Successful in 16s
QRun-IO/ci-policy: Shared Java Maven CI with Docker / verify (pull_request) Successful in 6m27s
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Codex <noreply@openai.com>
2026-11-02 15:45:12 +00:00
45b34b7ccd ci: move the QRun release caller to the thin v2 contract
QRun-IO/ci-policy: ci / java-frontend-browser-3 (pull_request) Skipped
QRun-IO/ci-policy: ci / node-5 (pull_request) Skipped
QRun-IO/ci-policy: ci / node-6 (pull_request) Skipped
QRun-IO/ci-policy: ci / node-playwright-8 (pull_request) Skipped
QRun-IO/ci-policy: ci / source-config-9 (pull_request) Skipped
Shared Java Maven CI with Docker / secrets (push) Failing after 19s
QRun-IO/ci-policy: ci / java-docker-2 (pull_request) Canceled after 1h26m36s
Shared Java Maven CI with Docker / gate (push) Canceled after 0s
qrun-release / release (push) Canceled after 1h26m37s
Shared Java Maven CI with Docker / verify (push) Successful in 7m52s
QRun-IO/ci-policy: ci / fork-guard (pull_request) Skipped
QRun-IO/ci-policy: ci / head-profile (pull_request) Skipped
QRun-IO/ci-policy: ci / application-template-0 (pull_request) Skipped
QRun-IO/ci-policy: ci / gradle-1 (pull_request) Skipped
QRun-IO/ci-policy: ci / java-docker-4 (pull_request) Skipped
QRun-IO/ci-policy: ci / java-frontend-browser-5 (pull_request) Skipped
QRun-IO/ci-policy: ci / java-maven-6 (pull_request) Skipped
QRun-IO/ci-policy: ci / java-maven-7 (pull_request) Skipped
QRun-IO/ci-policy: ci / node-8 (pull_request) Skipped
QRun-IO/ci-policy: ci / node-10 (pull_request) Skipped
QRun-IO/ci-policy: ci / source-config-12 (pull_request) Skipped
QRun-IO/ci-policy: ci / source-config-13 (pull_request) Skipped
QRun-IO/ci-policy: ci / not-enrolled (pull_request) Skipped
QRun-IO/ci-policy: native / fork-deny (pull_request) Skipped
QRun-IO/ci-policy: native / dotnet-0 (pull_request) Skipped
QRun-IO/ci-policy: native / native-result (pull_request) Successful in 9s
QRun-IO/ci-policy: ci / repository-policy (pull_request) Skipped
QRun-IO/ci-policy: ci / java-core-2-head (pull_request) Skipped
QRun-IO/ci-policy: ci / java-docker-3-head (pull_request) Skipped
QRun-IO/ci-policy: ci / java-maven-6-head (pull_request) Skipped
QRun-IO/ci-policy: ci / java-maven-7-head (pull_request) Skipped
QRun-IO/ci-policy: Shared Java Maven CI with Docker / gate (pull_request) Successful in 14s
QRun-IO/ci-policy: ci / java-docker-3 (pull_request) Successful in 10m30s
QRun-IO/ci-policy: ci / ci-result (pull_request) Successful in 7s
QRun-IO/ci-policy: ci / java-maven-4 (pull_request) Skipped
QRun-IO/ci-policy: ci / node-7 (pull_request) Skipped
QRun-IO/ci-policy: ci / node-9 (pull_request) Skipped
QRun-IO/ci-policy: ci / node-playwright-11 (pull_request) Skipped
QRun-IO/ci-policy: Shared Java Maven CI with Docker / secrets (pull_request) Successful in 22s
QRun-IO/ci-policy: Shared Java Maven CI with Docker / verify (pull_request) Successful in 7m29s
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Codex <noreply@openai.com>
2026-11-02 06:15:41 +00:00
forge-recoveryandCodex 2652843c29 ci: add the identical QRun release caller
QRun-IO/ci-policy: ci / node-3 (pull_request) Skipped
QRun-IO/ci-policy: ci / node-4 (pull_request) Skipped
QRun-IO/ci-policy: ci / not-enrolled (pull_request) Skipped
QRun-IO/ci-policy: ci / fork-guard (pull_request) Skipped
QRun-IO/ci-policy: ci / gradle-0 (pull_request) Skipped
QRun-IO/ci-policy: ci / java-maven-2 (pull_request) Skipped
QRun-IO/ci-policy: ci / source-config-5 (pull_request) Skipped
QRun-IO/ci-policy: Shared Java Maven CI with Docker / gate (pull_request) Failing after 10s
QRun-IO/ci-policy: ci / java-docker-1 (pull_request) Failing after 12m22s
QRun-IO/ci-policy: ci / ci-result (pull_request) Failing after 5s
QRun-IO/ci-policy: Shared Java Maven CI with Docker / secrets (pull_request) Failing after 21s
QRun-IO/ci-policy: Shared Java Maven CI with Docker / verify (pull_request) Successful in 7m9s
Co-Authored-By: Codex <noreply@openai.com>
2026-11-02 03:25:36 +00:00
james.maesandClaude Fable 5.1 ca9212731d fix(api): say NOT_CONFIGURED, with what to configure, when no model provider is usable (#83)
QRun-IO/ci-policy: ci / fork-guard (push) Skipped
QRun-IO/ci-policy: ci / gradle-0 (push) Skipped
QRun-IO/ci-policy: ci / java-maven-2 (push) Skipped
QRun-IO/ci-policy: ci / node-3 (push) Skipped
QRun-IO/ci-policy: ci / node-4 (push) Skipped
QBit CodeQL / Analyze branch source (push) Canceled after 0s
CodeQL / analyze (push) Canceled after 0s
QRun-IO/ci-policy: Shared Java Maven CI with Docker / secrets (push) Failing after 21s
QRun-IO/ci-policy: Shared Java Maven CI with Docker / gate (push) Failing after 8s
QRun-IO/ci-policy: ci / java-docker-1 (push) Failing after 37m15s
QRun-IO/ci-policy: ci / ci-result (push) Failing after 5s
QRun-IO/ci-policy: ci / not-enrolled (push) Skipped
QRun-IO/ci-policy: ci / source-config-5 (push) Skipped
QRun-IO/ci-policy: Shared Java Maven CI with Docker / verify (push) Successful in 7m45s
The probe reports DISABLED, NOT_CONFIGURED, NO_PERMISSION, KILL_SWITCH in that order; GET /config gains an additive message naming what to configure; a user without access on an unconfigured server gets 503 NOT_CONFIGURED instead of 403. Extension 1.7.0.

Fixes #82

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-11-01 11:30:04 -06:00
james.maesandCodex bcf173f4f2 ci: adopt managed orb 0.8 patch track (#78)
Co-authored-by: Codex <noreply@openai.com>
2026-10-21 15:31:42 -05:00
james.maesandCodex 5a1985fd96 ci: adopt shared orb 0.7.18 (#77)
Co-authored-by: Codex <noreply@openai.com>
2026-10-21 14:48:54 -05:00
james.maesandCodex 055df2e764 ci: adopt shared orb 0.7.17 (#76)
Co-authored-by: Codex <noreply@openai.com>
2026-10-21 14:20:22 -05:00
james.maesandCodex 0f79e7352a ci: use the shared branch-aware CodeQL worker (#74)
Co-authored-by: Codex <noreply@openai.com>
2026-10-21 10:51:16 -05:00
14 changed files with 304 additions and 18 deletions

No files matched your search

+2 -2
View File
@@ -1,12 +1,12 @@
version: 2.1
setup: true
orbs:
qqq-orb: kingsrook/qqq-orb@0.7.12
qqq-orb: kingsrook/qqq-orb@0.8
workflows:
setup:
jobs:
- qqq-orb/generate_pipeline:
orb_version: 0.7.12
orb_version: '0.8'
filters:
branches:
ignore: automation/qbit-rc-pending
+25
View File
@@ -0,0 +1,25 @@
# Standard QRun release caller (v2, thin contract): every push and manual dispatch delegates to the protected qrun-release-v1 channel. Do not edit per repository.
name: qrun-release
on:
push: {}
workflow_dispatch: {}
permissions:
contents: read
actions: read
jobs:
release:
uses: self:KofTwentyTwo/platform-ci/.gitea/workflows/java-docker.yaml@qrun-release-v1
with:
runner: linux-small
docker-runner: container-build
browsers: true
browser-runner: browser-build
postpublish:
if: ${{ needs.release.outputs.capture == 'true' }}
needs: release
permissions:
contents: write
actions: write
uses: self:KofTwentyTwo/platform-ci/.gitea/workflows/qrun-source-version.yaml@qrun-release-v1
with:
runner: linux-small
+72
View File
@@ -0,0 +1,72 @@
name: Release candidate
'on':
workflow_dispatch:
inputs:
dry_run:
description: Plan a cut without changing release state
type: boolean
default: false
finalize:
description: Retry finalization of an existing cut; do not cut another RC
type: boolean
default: false
recover:
description: Recover the same published RC after a deployment timeout; never
reupload
type: boolean
default: false
circle_build:
description: Original release run id (recovery only)
type: string
default: ''
merge_sha:
description: Existing cut merge_sha (finalize retry only)
type: string
default: ''
bump_sha:
description: Existing cut bump_sha (finalize retry only)
type: string
default: ''
branch:
description: Existing cut branch (finalize retry only)
type: string
default: ''
version:
description: Existing cut version (finalize retry only)
type: string
default: ''
pull_request:
types:
- closed
branches:
- develop
permissions:
contents: read
jobs:
release-rc:
# The old qbit-rc-published repository_dispatch is replaced by the already-proven workflow_dispatch
# completion: the publisher's own ActionsWRITE job dispatches this workflow with finalize=true plus
# merge_sha/bump_sha/branch/version (same inputs as the manual finalize retry).
permissions:
contents: write
actions: read
pull-requests: write
if: github.event_name == 'workflow_dispatch'
|| (github.event_name == 'pull_request' && github.event.pull_request.merged
== true && github.event.pull_request.base.ref == 'develop' && contains(github.event.pull_request.labels.*.name,
'release:rc'))
uses: self:KofTwentyTwo/platform-ci/.gitea/workflows/qbit-release-rc.yaml@qrun-release-v1
with:
dry_run: ${{ github.event_name == 'workflow_dispatch' && inputs.dry_run || false
}}
finalize: ${{ github.event_name == 'workflow_dispatch' && inputs.finalize }}
recover: ${{ github.event_name == 'workflow_dispatch' && inputs.recover || false
}}
circle_build: ${{ github.event_name == 'workflow_dispatch' && inputs.circle_build
|| '' }}
merge_sha: ${{ inputs.merge_sha || '' }}
bump_sha: ${{ inputs.bump_sha || '' }}
branch: ${{ inputs.branch || '' }}
version: ${{ inputs.version || '' }}
secrets:
QRUN_TAG_SIGNING_KEY: ${{ secrets.QRUN_TAG_SIGNING_KEY }}
+1 -1
View File
@@ -1,5 +1,5 @@
profile: qbit
orb_version: 0.7.12
orb_version: '0.8'
default_branch: develop
java_version: '21'
browsers: true
+12
View File
@@ -1,5 +1,16 @@
# Changelog
## Unreleased - Answer visible at the end of the stream
- The turn's redacted transcript (the question and answer parts, pending actions, stored events) is now written before `message.end` is sent. The panel reloads the conversation the moment the stream ends, and used to read the empty placeholders (`[]`) the transcript redactor stores until its flush, so the newest question and answer showed as empty bubbles until the panel was closed and reopened.
## Unreleased - Clear reason without a model provider (UI API extension 1.7.0)
- `GET /config` checks the server-wide causes before the user's permission: `DISABLED`, then `NOT_CONFIGURED` (no usable model provider), then `NO_PERMISSION`, then `KILL_SWITCH`. A server with no provider no longer tells users to ask for access (#82).
- UI API extension assistant 1.7.0: `GET /config` has `message` whenever `reason` is set: a sentence for the user. For `NOT_CONFIGURED` it says what an administrator configures (an enabled row on **Assistant > Providers**, for example Anthropic with `ANTHROPIC_API_KEY` set in the server environment, or a local OpenAI-compatible server such as Ollama).
- Without a usable provider, calls from a user who also lacks `assistant.hasAccess` answer `503 NOT_CONFIGURED` with that message instead of `403 NO_PERMISSION`; users with access keep their conversations, and a new question answers `503 NOT_CONFIGURED`.
- Verified the OpenAI-compatible adapter (kind `OLLAMA`) live against a local Ollama `llama3.3:70b-instruct-q4_K_M`, including a tool call (`LiveOpenAiCompatibleTest`).
## Unreleased - Conversation management (UI API extension 1.6.0)
- `assistant_conversation` is open to every assistant user (`assistant.hasAccess` or `assistant.admin`, through the new `AssistantAccessPermissionChecker`) in the standard table UI. Rows stay locked to their owner by the `userId` record lock, so a user only ever sees, updates and deletes their own conversations; admins with the all-access key still see everyone's. The Assistant app (its nav) and every other assistant table stay admin-only.
@@ -27,6 +38,7 @@
- Provider SPI: `ModelPart.Type.IMAGE` (`mediaType`, base64 `data`), mapped by the Anthropic, Bedrock and OpenAI-compatible providers. Claude models report vision. `ModelPart.toString()` never includes text or image data.
- `GET /config`: `features.attachments`, `limits.maxAttachmentBytes`, `limits.maxAttachments`, `limits.attachmentTypes`.
- System prompt: when `create_record` / `update_record` are offered, the model proposes the change instead of saying it cannot create records.
||||||| parent of c5125f9 (fix(assistant): store the transcript before message.end so the panel shows the answer)
## Unreleased - Optional provider health
+11
View File
@@ -479,6 +479,17 @@ OLLAMA, LM_STUDIO, VLLM, LLAMA_CPP or OPENAI_COMPATIBLE as their kind.
Registry prices override type/model prices, then configured prices. Unpriced
local models cost zero. Capabilities can be adjusted per registered model.
For a local Ollama with no key (the live test ran against `llama3.3:70b-instruct-q4_K_M`):
```java
config.withProviderType(new OpenAiCompatibleProviderType());
// provider row: type openai-compatible, kind OLLAMA, model llama3.3:70b-instruct-q4_K_M,
// base URL empty (http://localhost:11434/v1) or an allow-listed override such as http://127.0.0.1:11434/v1
```
Without any usable provider `GET /config` answers `enabled: false, reason: NOT_CONFIGURED` with a
`message` that says what to configure, before any permission check.
For Ollama behind a TLS proxy with a private CA and bearer authentication:
```java
+1 -1
View File
@@ -44,7 +44,7 @@ Guards:
`ProviderService.resolve(policyModel)`:
1. The enabled row marked default (marking one default unmarks the others), else the first enabled row.
2. With no enabled row, the provider registered in code (`withModelProvider`) and the policy's main model: hosts that never add a row keep working.
3. A row that cannot be used (no key, a disallowed name or URL, an uninstalled type) makes the assistant report `NOT_CONFIGURED` rather than fall back silently.
3. A row that cannot be used (no key, a disallowed name or URL, an uninstalled type) makes the assistant report `NOT_CONFIGURED` rather than fall back silently. `GET /config` reports it before `NO_PERMISSION`, with a `message` naming what to configure (#82).
The model is the row's, else the policy's main model. The rows and prices are cached for `policyCacheSeconds` and dropped on any change.
@@ -86,6 +86,20 @@ public class AssistantApi
public static final int TITLE_MAX_CHARS = 60;
/////////////////////////////////////////////////////////////////////////////
// GET /config reason (and error code) when no model provider is usable: //
// no enabled provider row that can be built (a missing key, a disallowed //
// variable or URL, an uninstalled type) and no provider registered in code //
/////////////////////////////////////////////////////////////////////////////
public static final String NOT_CONFIGURED = "NOT_CONFIGURED";
public static final String NO_MODEL_PROVIDER_MESSAGE = "No model provider is configured on this server. An administrator adds an enabled provider on "
+ "Assistant > Providers (for example type Anthropic, whose API key variable ANTHROPIC_API_KEY must be set in the server environment, or a local "
+ "OpenAI-compatible server such as Ollama).";
public static final String NO_PERMISSION_MESSAGE = "You do not have access to the assistant.";
public static final String DISABLED_MESSAGE = "The assistant is turned off for this application.";
public static final String KILL_SWITCH_MESSAGE = "The assistant has been turned off by an administrator.";
private final AssistantRuntime runtime;
private final AssistantQBitConfig config;
private final AssistantStore store;
@@ -134,27 +148,39 @@ public class AssistantApi
Map<String, Object> result = new LinkedHashMap<>();
String reason = null;
String message = null;
/////////////////////////////////////////////////////////////////////////
// server-wide causes come before the user's own permission, so a //
// server without a usable model provider says so (QRun-IO/qbit- //
// assistant#82) instead of sending the user to ask for access //
/////////////////////////////////////////////////////////////////////////
if(!config.isEffectivelyEnabled())
{
reason = "DISABLED";
message = DISABLED_MESSAGE;
}
else if(activeProvider() == null)
{
reason = NOT_CONFIGURED;
message = NO_MODEL_PROVIDER_MESSAGE;
}
else if(!hasAccess())
{
reason = "NO_PERMISSION";
}
else if(activeProvider() == null)
{
reason = "NOT_CONFIGURED";
message = NO_PERMISSION_MESSAGE;
}
else if(runtime.getPolicyService().current(false).killSwitch())
{
reason = "KILL_SWITCH";
message = KILL_SWITCH_MESSAGE;
}
result.put("enabled", reason == null);
if(reason != null)
{
result.put("reason", reason);
result.put("message", message);
}
result.put("shortcut", config.getShortcut());
result.put("model", reason == null ? activeProvider().model() : config.getModels().getMainModel());
@@ -425,11 +451,11 @@ public class AssistantApi
ProviderService.Active active = runtime.getProviderService().resolve(policy.mainModel());
if(active == null)
{
throw (new AssistantApiException(503, "NOT_CONFIGURED", "The assistant is not configured on this server."));
throw (new AssistantApiException(503, NOT_CONFIGURED, NO_MODEL_PROVIDER_MESSAGE));
}
if(policy.killSwitch())
{
throw (new AssistantApiException(503, "KILL_SWITCH", "The assistant has been turned off by an administrator."));
throw (new AssistantApiException(503, "KILL_SWITCH", KILL_SWITCH_MESSAGE));
}
Long capMicros = ProviderService.capMicros(active.row());
@@ -1165,11 +1191,33 @@ public class AssistantApi
{
if(!config.isEffectivelyEnabled())
{
throw (new AssistantApiException(503, "DISABLED", "The assistant is turned off for this application."));
throw (new AssistantApiException(503, "DISABLED", DISABLED_MESSAGE));
}
if(!hasAccess())
{
throw (new AssistantApiException(403, "NO_PERMISSION", "You do not have access to the assistant."));
if(activeProviderOrNull() == null)
{
throw (new AssistantApiException(503, NOT_CONFIGURED, NO_MODEL_PROVIDER_MESSAGE));
}
throw (new AssistantApiException(403, "NO_PERMISSION", NO_PERMISSION_MESSAGE));
}
}
/*******************************************************************************
** The active provider, or null when none is usable or it cannot be read
** (only used to pick the clearer refusal).
*******************************************************************************/
private ProviderService.Active activeProviderOrNull()
{
try
{
return (activeProvider());
}
catch(QException | RuntimeException e)
{
return (null);
}
}
@@ -63,7 +63,7 @@ import io.javalin.http.HttpStatus;
public final class AssistantSpecs
{
public static final String EXTENSION_NAME = "assistant";
public static final String EXTENSION_VERSION = "1.6.0";
public static final String EXTENSION_VERSION = "1.7.0";
public static final String PREFIX = "/assistant";
@@ -198,7 +198,8 @@ public final class AssistantSpecs
"starterPrompts", array(string(null), "Up to three suggested first questions in total: permitted configured questions first, then generated questions. Table, row and field access are checked on every request.")));
rs.put("AssistantConfigResponseV1", object("Whether the assistant is available to this user, and how.",
"enabled", bool("Whether the panel may be used."),
"reason", string("Why not: DISABLED, NO_PERMISSION, NOT_CONFIGURED or KILL_SWITCH.").withNullable(true),
"reason", string("Why not: DISABLED, NOT_CONFIGURED (no usable model provider on the server), NO_PERMISSION or KILL_SWITCH, checked in that order.").withNullable(true),
"message", string("Since 1.7.0: when reason is set, a sentence for the user that names the cause (for NOT_CONFIGURED, what an administrator configures).").withNullable(true),
"shortcut", string("Keyboard shortcut, e.g. mod+j."),
"model", string("The model that answers."),
"limits", freeObject("maxTurnSeconds and maxToolCallsPerTurn. When features.attachments is true also maxAttachmentBytes (the largest file read, in bytes), "
@@ -1343,6 +1343,17 @@ public class TurnEngine implements Runnable
String finalErrorText = errorText;
safely(() -> emit(TurnEvent.ERROR, map("code", finalErrorCode, "message", finalErrorText)));
}
///////////////////////////////////////////////////////////////////////
// the stream ends at message.end and the panel reloads the //
// conversation at once, so the redacted transcript (message parts, //
// pending actions, events) must be stored before that event is sent //
///////////////////////////////////////////////////////////////////////
if(!alreadyTerminal)
{
safely(this::persistPendingEvents);
safely(() -> transcript.flush(store));
}
safely(() -> emit(TurnEvent.MESSAGE_END, map("messageId", assistantMessage.getId(), "status", finalStatus, "stopReason", finalStopReason)));
if(!alreadyTerminal)
{
@@ -175,7 +175,13 @@ components:
description: "Whether the panel may be used."
type: "boolean"
reason:
description: "Why not: DISABLED, NO_PERMISSION, NOT_CONFIGURED or KILL_SWITCH."
description: "Why not: DISABLED, NOT_CONFIGURED (no usable model provider\
\ on the server), NO_PERMISSION or KILL_SWITCH, checked in that order."
nullable: true
type: "string"
message:
description: "Since 1.7.0: when reason is set, a sentence for the user that\
\ names the cause (for NOT_CONFIGURED, what an administrator configures)."
nullable: true
type: "string"
shortcut:
@@ -305,7 +311,9 @@ components:
nullable: true
type: "string"
starterPrompts:
description: "Up to three suggested first questions in total: permitted configured questions first, then generated questions. Table, row and field access are checked on every request."
description: "Up to three suggested first questions in total: permitted\
\ configured questions first, then generated questions. Table, row and\
\ field access are checked on every request."
items:
type: "string"
type: "array"
@@ -468,7 +476,7 @@ info:
description: "QQQ Assistant: conversations, streamed turns, feedback, pending actions\
\ and data access explanations"
title: "QQQ Middleware API extension: assistant"
version: "1.6.0"
version: "1.7.0"
openapi: "3.0.3"
paths:
/qqq/v1/assistant/config:
@@ -166,6 +166,45 @@ class AssistantRouteProviderTest extends BaseAssistantTest
/*******************************************************************************
** No usable model provider (QRun-IO/qbit-assistant#82): the probe names the
** server's configuration, for users with and without access, and calls
** answer 503 NOT_CONFIGURED instead of 403 NO_PERMISSION.
*******************************************************************************/
@Test
void testNoModelProviderIsNotAPermissionProblem() throws Exception
{
String providerName = config.getProviderName();
config.setProviderName("no-such-provider");
try
{
for(String user : new String[] {EDITOR, NO_ACCESS})
{
Map<String, Object> probe = json(send("GET", "/config", user, null));
assertThat(probe).containsEntry("enabled", false).containsEntry("reason", "NOT_CONFIGURED");
assertThat((String) probe.get("message")).contains("No model provider").contains("Assistant > Providers").contains("ANTHROPIC_API_KEY");
}
HttpResponse<String> denied = send("GET", "/conversations", NO_ACCESS, null);
assertThat(denied.statusCode()).isEqualTo(503);
assertThat(json(denied)).containsEntry("code", "NOT_CONFIGURED");
assertThat((String) json(denied).get("message")).contains("No model provider");
assertThat(send("GET", "/conversations", EDITOR, null).statusCode()).isEqualTo(200);
HttpResponse<String> turn = send("POST", "/conversations", EDITOR, "{}");
Object id = json(turn).get("id");
HttpResponse<String> ask = send("POST", "/conversations/" + id + "/turns", EDITOR, "{\"text\":\"hi\"}");
assertThat(ask.statusCode()).isEqualTo(503);
assertThat(json(ask)).containsEntry("code", "NOT_CONFIGURED");
}
finally
{
config.setProviderName(providerName);
}
}
/*******************************************************************************
**
*******************************************************************************/
@@ -178,7 +217,8 @@ class AssistantRouteProviderTest extends BaseAssistantTest
assertThat((Map<String, Object>) config.get("limits")).containsEntry("maxTurnSeconds", 300).containsEntry("maxToolCallsPerTurn", 25);
assertThat((Map<String, Object>) config.get("features")).containsEntry("writes", false);
assertThat(json(send("GET", "/config", NO_ACCESS, null))).containsEntry("enabled", false).containsEntry("reason", "NO_PERMISSION");
assertThat(json(send("GET", "/config", NO_ACCESS, null))).containsEntry("enabled", false).containsEntry("reason", "NO_PERMISSION")
.containsEntry("message", "You do not have access to the assistant.");
HttpResponse<String> denied = send("GET", "/conversations", NO_ACCESS, null);
assertThat(denied.statusCode()).isEqualTo(403);
assertThat(json(denied)).containsEntry("code", "NO_PERMISSION")
@@ -141,6 +141,27 @@ class TurnEngineTest extends BaseAssistantTest
/*******************************************************************************
** The panel reloads the conversation as soon as message.end arrives, so the
** question and the answer must already be stored then, not after it.
*******************************************************************************/
@Test
void testMessagesAreStoredBeforeMessageEnd() throws Exception
{
provider.thenText("Four posts are published.");
TurnRunner.Outcome outcome = runner.run(EDITOR, "how many published posts?");
assertThat(outcome.end()).containsEntry("status", "COMPLETE");
List<AssistantMessage> stored = runner.messagesAtEnd();
assertThat(stored).hasSize(2);
assertThat(stored).extracting(AssistantMessage::getPartsJson).allSatisfy(parts -> assertThat(parts).isNotEqualTo("[]"));
assertThat(stored.get(0).getPartsJson()).contains("how many published posts?");
assertThat(stored.get(1).getPartsJson()).contains("Four posts are published.");
}
/*******************************************************************************
** Parallel tool calls: all results in ONE user message, in order.
*******************************************************************************/
@@ -27,6 +27,7 @@ import com.kingsrook.qbits.assistant.AssistantRuntime;
import com.kingsrook.qbits.assistant.api.AssistantApi;
import com.kingsrook.qbits.assistant.engine.RunningTurn;
import com.kingsrook.qbits.assistant.engine.TurnEvent;
import com.kingsrook.qbits.assistant.model.AssistantMessage;
import com.kingsrook.qbits.assistant.model.AssistantToolCall;
import com.kingsrook.qbits.assistant.persistence.AssistantStore;
import com.kingsrook.qqq.backend.core.context.QContext;
@@ -47,6 +48,8 @@ public class TurnRunner
private final QInstance qInstance;
private final AssistantQBitConfig config;
private List<AssistantMessage> messagesAtEnd = List.of();
/*******************************************************************************
@@ -169,6 +172,10 @@ public class TurnRunner
{
List<TurnEvent> batch = running.getLog().after(last, 1000);
events.addAll(batch);
if(batch.stream().anyMatch(e -> TurnEvent.MESSAGE_END.equals(e.getType())))
{
messagesAtEnd = messagesOf(userId, convId);
}
if(!batch.isEmpty())
{
last = batch.get(batch.size() - 1).getSeq();
@@ -194,6 +201,36 @@ public class TurnRunner
/*******************************************************************************
** The conversation's stored messages, read the moment the run's message.end
** was seen: what the panel finds when it reloads the conversation at the end
** of the stream.
*******************************************************************************/
public List<AssistantMessage> messagesAtEnd()
{
return (messagesAtEnd);
}
/*******************************************************************************
** The stored messages of a conversation (read as the user).
*******************************************************************************/
private List<AssistantMessage> messagesOf(String userId, Integer conversationId) throws Exception
{
QContext.init(qInstance, AssistantTestFixture.ownerSession(userId, config));
try
{
return (new AssistantStore().listMessages(userId, conversationId, null, 50));
}
finally
{
QContext.clear();
}
}
/*******************************************************************************
** The audited tool calls of a turn (read as the user).
*******************************************************************************/