Repository navigation
v2.5.3
·
110 commits
to development
since this release
What's New
- The same model can now be offered by several providers (e.g.
gemma-4-31b-iton GWDG and Google). Models are identified by<provider_id>/<model_id>; previously the second provider's entry failed to sync.
Bugfix
php hawki initandphp hawki run -buildno longer run an unattendedcomposer updatewhen outdated packages are detected. Setup and build now only perform a deterministiccomposer install; upgrading packages is a deliberate decision. Previously the silent update could jump packages across major versions (e.g.laravel/ai0.10 → 1.3), breaking the app with errors that first surfaced in unrelated commands likemodels sync.- Self-service access token creation was unavailable in the profile view: it still checked the pre-2.5.0
sanctum.*config keys instead of the newexternal_access.*keys, so the AccessTokens panel never appeared even withALLOW_EXTERNAL_COMMUNICATIONandALLOW_USER_TOKEN_CREATIONenabled. - Security: Fixed a privilege escalation in group chat invitations. Any room member (including viewers) could create an invitation for themselves with the
adminrole and accept it to become a room admin, or overwrite other users' pending invitations. Sending invitations is now restricted to room admins, invitation roles are validated, and accepting an invitation no longer changes the role of an existing member. Thanks to Artur Chlebicki for responsibly reporting this issue! - The room controls are now fully restricted for room admins.
- Removing or leaving a room now deletes the member's pending invitations for that room, and leaving a room no longer fails halfway.
- An admin leaving a room no longer deletes the whole room when other admins remain. The last admin of a room can't leave it anymore, only delete it.
- Members can no longer change their own role, and the role of HAWKI can't be changed. Members can only be added to a room through invitations.
- Model enrichment (pricing, context windows, feature flags) stopped updating because LiteLLM discontinued their catalog API. Lookups always fell back to the static files. The catalog is now read from LiteLLM's GitHub file.
- Fixed attachments never reaching the AI conversation when
STORAGE_DISKuses an s3-compatible server (e.g. garage, MinIO): uploaded files were never persisted from the temp area because whole "directory" moves are silently ignored by s3 drivers, so the attachment was never linked to the message. Each file is now copied individually instead. - The
s3disk inconfig/filesystems.phpnow fails loudly on storage errors and works on s3 servers without ACL support (see Internals section for details). - The
check:storageartisan command now respects the configuredS3_ENDPOINTand path-style addressing instead of always probingamazonaws.com. - Tool-call progress never reached the UI during streaming because the stream handler matched the tool-call data class instead of the stream event. Tool calls are now reported for every provider as
tool_call/tool_resultpackets. - MCP tools failed because processes were decoupled using
autoSse=true(the default). New default isautoSse=false. - Per-provider AI request timeouts via the
adaptersettings inmodel_providers.php:request_timeout(default 60 seconds,0= no limit) limits the complete answer of non-streaming requests.stall_timeout(default off) aborts a stream that receives no data for this many seconds, before or mid-stream — like HAWKI 2.4, streams are never cut off while they receive data, and silent streams are no longer aborted after 60 seconds. Supported drivers for stall detection:openai-compatible,ollama. GWDG ships with the HAWKI 2.4 behaviour (request_timeout=0,stall_timeout=120, viaGWDG_REQUEST_TIMEOUT/GWDG_STALL_TIMEOUT). - Large chat messages fail to encrypt with a
RangeError: too many arguments provided for a function call: the client-side encryption encoded the ciphertext with oneString.fromCharCode.apply()over every byte, which exceeds the browser's argument limit at roughly 100k bytes. Base64 conversion now runs in 32k chunks, in both the encryption utils and the legacy bundle. - Multiple Mermaid diagram previews failed to render at the same time: generated diagrams could receive duplicate render IDs, causing the renderer to silently draw only one and skip the rest. Fixed upstream in
markstream2.0.15.
Internals
ai_models.model_idis now unique per provider instead of globally;system_modelsreferences models via the newai_model_idcolumn,system_models.model_idwas removed.- Inside HAWKI a model is only referenced by its provider-qualified identifier
<provider_id>/<model_id>(or its numeric id). This includes the JSON:APImodel_idofai-modelsandsystem-models, thesystem-modelsmodel_idfilter, chat requests on/req/streamAI, and themodelstored with messages and usage records. - A migration converts the stored model references (
ai_conv_msgs.model,messages.model,usage_records.model) to the qualified identifier. References to models that no longer exist are left unchanged. - The external app API (
/api/ai-req) still accepts a plainmodel_idas long as only one provider offers it; otherwise the request is rejected with400and asks the client to use the<provider_id>/<model_id>format. A model that does not exist is rejected with400as well. - New
ModelFinder(App\Services\Ai\Models\Identifiers\ModelFinder) resolves model references,AiModelIdentifierbuilds and parses the qualified form. - Provider keys in
model_providers.phpmust not contain/, the config sync reports an error for them. - The model lists (
config/model_lists/*.php) keep their format; the config sync combines the provider key with the model ID. - System models in
model_providers.php(system_models,default_models, and the*_MODELenv variables) keep their format and also accept<provider_id>/<model_id>. If a plain model ID is offered by multiple providers, the config sync uses the model that was created first and logs a warning recommending the qualified ID (e.g.openAi/gpt-5-nano). ai:tools:assign --modelandusage:top-users --modelaccept the qualified identifier; a plain model ID matches the model of every provider offering it.- Breaking for custom code:
AiModelRepository::findOne()/findOneOrFail()andModelFinder::find()no longer resolve a plainmodel_id, pass<provider_id>/<model_id>or useAiModelRepository::findOneByProvider().SystemModel::$model_idwas removed, useai_model_id/SystemModel::model(). - The
laravel/aiversion constraint is now^0.10.1(was>=0.10.1), so dependency updates stay within semver-compatible releases instead of silently allowing a major jump. Lock file refreshed tolaravel/aiv0.10.3. - LiteLLM catalog: downloaded once into a shared cache and filtered per provider, kept fresh via ETag revalidation every 5 minutes. During upstream outages cached data is served for up to 24 h, then the static files take over.
- The
static_llm_dataupdater no longer deletes the old files when the catalog is unreachable. - Added
RoomRepositoryandInvitationRepository(App\Services\Chat\Room\Repositories). Room and invitation queries inRoomService,InvitationController,StreamController, theroomAdmin/roomEditormiddlewares, the room broadcast channel and themigrate:avatarscommand now go through these repositories instead of Eloquent statics. - Replaced the unused
POST /req/room/addMember/{slug}endpoint, which could add members without a room key, withPOST /req/room/changeMemberRole/{slug}(route nameweb.roomEditMemberunchanged). - Storage: driver-agnostic file persistence (
app/Services/Storage/AbstractFileStorage.php):persistTemporaryFilepreviously moved the whole temp directory, which only works on local disks (viarename()); s3-style drivers treat directories as key prefixes and silently ignore such moves. It now copies each file individually (allFiles()+copy()+ onedeleteDirectory()), which Laravel's contract guarantees for every driver — a server-sideCopyObjecton s3, a native copy on local disks. - Storage: hardened s3 disk config (
config/filesystems.php):throw => trueturns failed Flysystem operations into logged exceptions instead of uncheckedfalsereturns (the reason the original bug stayed invisible);retain_visibility => falseskips theGetObjectAclpre-check before eachcopy(), which ACL-less s3 servers (garage, Cloudflare R2) reject. - Storage:
check:storagenow honors custom s3 endpoints (app/Console/Commands/CheckStorageConnection.php) by forwarding the disk'sendpoint/use_path_style_endpointto itsS3Clientinstead of always probingamazonaws.com. AbstractLaravelAgentpasses the timeout explicitly to the Laravel AI package:send()the request timeout,sendStreaming()the stall timeout (or no limit when stall detection is off). The newAgentTimeoutsvalue object reads and validates both values. For streamed requests, Guzzle uses its PHP stream handler, wheretimeoutis a per-read timeout and curl options are ignored.