* fix: init WASM runtime eagerly regardless of tools directory existence
The WASM tool runtime was only created at startup when both
`wasm.enabled` and `wasm.tools_dir.exists()` were true. This meant
that if the tools directory didn't exist yet (e.g. fresh deploy with
`--no-onboard`), the runtime was set to None and passed to the
ExtensionManager. Extensions installed later via the web UI would
then fail with "WASM runtime not available" because the runtime
could not be retroactively created.
The Wasmtime engine initialization has no dependency on the tools
directory — it only configures the compiler and starts an epoch
ticker thread. The directory is only needed later when loading
.wasm modules. Remove the directory check so the runtime is
available for post-startup extension activation.
Co-Authored-By: Claude Opus 4.6 <[email protected]>
* test: add regression tests for WASM runtime eager init
- runtime.rs: test_runtime_creation_without_tools_dir confirms the
Wasmtime engine initialises without a tools directory on disk
- manager.rs: test_activate_wasm_tool_with_runtime_passes_runtime_check
verifies activation gets past the runtime check when a runtime is
provided (fails on missing file, not missing runtime)
- manager.rs: test_activate_wasm_tool_without_runtime_fails_with_runtime_error
verifies the original error when no runtime is available
Co-Authored-By: Claude Opus 4.6 <[email protected]>
* refactor: use idiomatic Result-to-Option conversion for WASM runtime init
Address PR review feedback: replace match block with
.map(Arc::new).map_err(|e| warn!(...)).ok() chain.
Co-Authored-By: Claude Opus 4.6 <[email protected]>
* style: fix formatting in extension manager tests
Co-Authored-By: Claude Opus 4.6 <[email protected]>
---------
Co-authored-by: Claude Opus 4.6 <[email protected]>
* fix: make onboarding installs prefer release artifacts with source fallback
* fix: harden extension fallback errors and surface setup warnings
* fix: validate registry artifacts and harden fallback errors
* fix: address review feedback on installer fallback
- Add upfront validate_manifest_install_inputs() in
install_with_source_fallback so bad manifests fail fast without
relying on inner methods to catch them
- Document ALLOWED_ARTIFACT_HOSTS as GitHub-only by design
- Document intentional url omission from DownloadFailed Display
- Add channel manifest validation tests (wrong prefix rejected,
correct prefix accepted)
Co-Authored-By: Claude Opus 4.6 <[email protected]>
* fix: require SHA256 checksum for artifact downloads
Reject artifact installs when the manifest has sha256: null instead of
warning and proceeding. This prevents installing unverified pre-built
binaries during onboarding. The check runs before downloading to avoid
wasting bandwidth.
Since InvalidManifest blocks source fallback, manifests with URLs but
no checksums will hard-fail rather than silently falling back to source
build — forcing the manifest to be fixed.
The release CI already computes SHA256 for each bundle; the manifests
just need to be populated with the actual values.
Co-Authored-By: Claude Opus 4.6 <[email protected]>
* fix: enforce SHA256 checksums and auto-patch manifests in CI
- Fix cargo fmt on SHA256 check code
- Reorder release CI: build WASM extensions before binary so manifests
can be patched with computed SHA256 before build.rs embeds them
- Add "Patch manifests with WASM checksums" step in build-local-artifacts
that reads checksums.txt and updates registry JSON files before building
- Add update-registry-checksums job that commits patched manifests back
to main after release, keeping the repo in sync with released artifacts
This closes the integrity gap where all manifests had sha256: null and
artifact downloads were unverified. The binary now embeds correct SHA256
values and the installer hard-rejects null checksums.
Co-Authored-By: Claude Opus 4.6 <[email protected]>
---------
Co-authored-by: Claude Opus 4.6 <[email protected]>
Co-authored-by: Bowen Wang <[email protected]>
* fix: make Telegram status prompts reliable
Approval and auth prompts could be missed when polling or reply-context sends failed, leaving users stuck in waiting states. This adds explicit status mapping and retries, keeps typing active through intermediate work while suppressing noisy tool telemetry, and adds regression tests plus CI coverage for the Telegram channel crate.
* fix: normalize terminal status handling
Terminal status strings from the agent loop can vary in casing and formatting, which could leak internal status lines to Telegram. This normalizes Done/Interrupted mapping and filters terminal status text consistently to keep chat UX clean while preserving actionable prompts.
---------
Co-authored-by: Claude Opus 4.6 <[email protected]>
* fix: persist WASM channel workspace writes across callbacks
WASM channel callbacks (polling, webhooks, on_start) call
workspace_write() to persist state, but the host code never committed
these writes — take_pending_writes() was never called. Additionally,
no WorkspaceReader was injected into channel capabilities, so
workspace_read() always returned None.
This caused Telegram's polling offset to reset to 0 on every tick,
making getUpdates re-deliver already-processed messages and producing
2-4 duplicate LLM responses per user message.
Add ChannelWorkspaceStore (Arc-wrapped HashMap with std::sync::RwLock)
that persists across callback invocations within a channel's lifetime.
Inject it as the WorkspaceReader and commit pending writes after every
callback execution (on_start, on_poll, on_http_request, execute_poll).
Co-Authored-By: Claude Opus 4.6 <[email protected]>
* style: fix formatting
Co-Authored-By: Claude Opus 4.6 <[email protected]>
---------
Co-authored-by: Claude Opus 4.6 <[email protected]>
The agentic loop injected fake user messages ("Please proceed and use
the available tools to complete this task.") when the LLM responded
with text instead of tool calls. This caused hallucinated conversations
during casual chat, 3x wasted LLM calls, and trust issues.
Remove the `resume_after_tool` parameter and `tools_executed` tracking
entirely. Text responses now return immediately, trusting the LLM to
decide when tools are needed (consistent with ZeroClaw and OpenClaw).
Closes#145
Co-authored-by: Claude Opus 4.6 <[email protected]>
* ci: add automated PR labeling system
Add two independent workflows for PR auto-labeling:
- Scope labels via actions/labeler (path glob matching)
- Size, risk, and contributor tier via custom shell script
Includes idempotent label bootstrap script (create-labels.sh).
Co-Authored-By: Claude Opus 4.6 <[email protected]>
* ci: temporarily use pull_request trigger for testing
Switch to pull_request so workflows run from the PR branch.
Will revert to pull_request_target before merge.
Co-Authored-By: Claude Opus 4.6 <[email protected]>
* fix(ci): use absolute path for search/issues API call
gh api requires a leading slash for REST endpoints.
Co-Authored-By: Claude Opus 4.6 <[email protected]>
* fix(ci): use gh pr list instead of search API for contributor count
The search/issues API returns 404 with the default GITHUB_TOKEN.
gh pr list --state merged works with standard permissions.
Co-Authored-By: Claude Opus 4.6 <[email protected]>
* ci: revert to pull_request_target for fork PR support
Restore pull_request_target trigger and base branch checkout
now that testing is complete.
Co-Authored-By: Claude Opus 4.6 <[email protected]>
---------
Co-authored-by: Claude Opus 4.6 <[email protected]>
* feat: add bundled and declarative hook bundle loading
* fix: load plugin hooks only for active extensions
* fix: avoid duplicate plugin hook registration
* security: harden outbound webhook hooks
* fix: pin webhook DNS resolutions for outbound hooks
* fix: block IPv4-mapped local webhook targets
* style: format webhook hardening changes for CI
* fix: pass HookRegistry to ExtensionManager in AppBuilder
After merging main (which extracted AppBuilder from main.rs in #198),
the ExtensionManager::new() call in app.rs was missing the `hooks`
parameter that PR #176 added. This moves HookRegistry creation before
init_extensions() and threads it through, matching the existing pattern
in main.rs.
Co-Authored-By: Claude Opus 4.6 <[email protected]>
---------
Co-authored-by: Illia Polosukhin <[email protected]>
Co-authored-by: Claude Opus 4.6 <[email protected]>
These are local tool data directories (Sidecar) that should not be
tracked. Added both to .gitignore to prevent future accidents.
Co-authored-by: Claude Opus 4.6 <[email protected]>
* feat: Add lifecycle hooks system with 6 interception points
Implement extensible hook infrastructure for intercepting and transforming
agent operations at well-defined points in the lifecycle:
- BeforeInbound: intercept/modify/reject incoming user messages
- BeforeToolCall: intercept/modify/reject tool executions (chat + job)
- BeforeOutbound: intercept/modify/suppress outgoing responses
- TransformResponse: transform final response before completing a turn
- OnSessionStart: fire-and-forget notification on new session creation
- OnSessionEnd: fire-and-forget notification on session pruning
Hooks execute in priority order with modification chaining, reject
short-circuits, configurable failure modes (FailOpen/FailClosed),
and per-hook timeouts. Empty registry is zero-cost (all hooks pass
through immediately).
Co-Authored-By: Claude Opus 4.6 <[email protected]>
* fix: enforce hook fail-closed semantics
* Merge upstream/main into feat/hooks-system-clean
Resolve merge conflicts:
- FEATURE_PARITY.md: Keep both upstream cron/routines status and hooks status
- src/error.rs: Keep both Hook and Orchestrator/Worker error variants
Co-Authored-By: Claude Opus 4.6 <[email protected]>
* fix: resolve CI test failures in pairing store and wizard
- Fix pairing store truncate bug: record_failed_approve used
.truncate(true) which wiped the file before reading, causing rate
limiting to never accumulate past 1 attempt. Changed to
.truncate(false) to preserve existing data.
- Fix wizard test: skip test_install_missing_bundled_channels when
telegram WASM artifact specifically isn't available, not just when
all channels are empty (whatsapp may exist without telegram).
- Add workspace exclude for subcrate directories to prevent cargo
from discovering them as workspace members during builds.
Co-Authored-By: Claude Opus 4.6 <[email protected]>
* fix: address PR #18 review comments
- Remove duplicate maybe_hydrate_thread call (rebase artifact)
- Fix RwLock held across async hook execution in HookRegistry::run()
- Add tracing::warn for silent JSON parse failures in hook modifications
- Refactor execute_tool_inner to accept &WorkerDeps instead of 8 Arc params
- Use real user_id from JobContext instead of job_id UUID in BeforeToolCall hook
Co-Authored-By: Claude Opus 4.6 <[email protected]>
* fix: cargo fmt + remove tracked worktree breaking CI
- Apply rustfmt formatting (method chain line breaks, match arm style)
- Remove .claude/worktrees/ from git tracking (caused submodule error in CI)
- Add .claude/worktrees/ to .gitignore
Co-Authored-By: Claude Opus 4.6 <[email protected]>
---------
Co-authored-by: Firat Sertgoz <[email protected]>
Co-authored-by: Claude Opus 4.6 <[email protected]>
* Add WebSocket gateway and control plane endpoint
Adds bidirectional WebSocket transport to the web gateway alongside
the existing SSE stream. Clients can send messages, approvals, and
pings over a single persistent connection at /api/chat/ws.
- Enable axum `ws` feature for built-in WebSocket support
- Add WsClientMessage/WsServerMessage types with tagged JSON protocol
- Add subscribe_raw() to SseManager for non-SSE consumers
- Create ws.rs with connection handler (split sender/receiver tasks)
- Add WsConnectionTracker for active connection counting
- Add /api/gateway/status control plane endpoint (SSE + WS counts)
- 35 new tests covering message types, broadcast, and handler logic
https://claude.ai/code/session_01KEaLN6Xq2j5EeV3SGHQT6b
* Add e2e WebSocket gateway integration tests
- Add tokio-tungstenite dev-dependency for WebSocket client in tests
- Update start_server to return actual bound SocketAddr (enables port 0)
- Add 10 e2e tests covering full HTTP upgrade → WebSocket → message flow:
ping/pong, message routing to agent, broadcast event delivery,
connection tracking, invalid message handling, auth rejection,
gateway status endpoint, and multi-event sequencing
https://claude.ai/code/session_01KEaLN6Xq2j5EeV3SGHQT6b
---------
Co-authored-by: Claude <[email protected]>