Add support for NVIDIA's API (https://integrate.api.nvidia.com/v1) with three models:
- nvidia/llama-3.1-nemotron-70b-instruct (default)
- nvidia/llama-3.3-70b-instruct
- nvidia/mistral-nemo-minitron-8b-8k-instruct
Users can configure via NVIDIA_API_KEY environment variable or auth profiles.
Co-authored-by: thesomewhatyou <162917831+thesomewhatyou@users.noreply.github.com>
* fix(venice): disable streaming to prevent SDK crash with usage-only chunks (#15819)
Venice.ai API returns SSE chunks containing only usage metadata without
a choices array. The SDK crashes trying to access choices[0] on these
chunks with: Cannot read properties of undefined (reading '0')
Changes:
- Disable streaming by default for all Venice models
- Apply to both static catalog and dynamically discovered models
- Users can explicitly enable streaming in config if needed
This is a workaround until the SDK handles Venice's streaming format.
Fixes#15819
* fix(venice): avoid usage streaming chunks for Venice models (openclaw#15878) thanks @Shuai-DaiDai
---------
Co-authored-by: 帅小呆1号 <shuaixiaodai1@openclaw.ai>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* feat: add before_compaction and before_reset plugin hooks with session context
- Pass session messages to before_compaction hook
- Add before_reset plugin hook for /new and /reset commands
- Add sessionId to plugin hook agent context
* feat: extraBootstrapFiles config with glob pattern support
Add extraBootstrapFiles to agent defaults config, allowing glob patterns
(e.g. "projects/*/TOOLS.md") to auto-load project-level bootstrap files
into agent context every turn. Missing files silently skipped.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(status): show custom memory plugins as enabled, not unavailable
The status command probes memory availability using the built-in
memory-core manager. Custom memory plugins (e.g. via plugin slot)
can't be probed this way, so they incorrectly showed "unavailable".
Now they show "enabled (plugin X)" without the misleading label.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: use async fs.glob and capture pre-compaction messages
- Replace globSync (node:fs) with fs.glob (node:fs/promises) to match
codebase conventions for async file operations
- Capture session.messages BEFORE replaceMessages(limited) so
before_compaction hook receives the full conversation history,
not the already-truncated list
* fix: resolve lint errors from CI (oxlint strict mode)
- Add void to fire-and-forget IIFE (no-floating-promises)
- Use String() for unknown catch params in template literals
- Add curly braces to single-statement if (curly rule)
* fix: resolve remaining CI lint errors in workspace.ts
- Remove `| string` from WorkspaceBootstrapFileName union (made all
typeof members redundant per no-redundant-type-constituents)
- Use type assertion for extra bootstrap file names
- Drop redundant await on fs.glob() AsyncIterable (await-thenable)
* fix: address Greptile review — path traversal guard + fs/promises import
- workspace.ts: use path.resolve() + traversal check in loadExtraBootstrapFiles()
- commands-core.ts: import fs from node:fs/promises, drop fs.promises prefix
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: resolve symlinks before workspace boundary check
Greptile correctly identified that symlinks inside the workspace could
point to files outside it, bypassing the path prefix check. Now uses
fs.realpath() to resolve symlinks before verifying the real path stays
within the workspace boundary.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: address Greptile review — hook reliability and type safety
1. before_compaction: add compactingCount field so plugins know both
the full pre-compaction message count and the truncated count being
fed to the compaction LLM. Clarify semantics in comment.
2. loadExtraBootstrapFiles: use path.basename() for the name field
so "projects/quaid/TOOLS.md" maps to the known "TOOLS.md" type
instead of an invalid WorkspaceBootstrapFileName cast.
3. before_reset: fire the hook even when no session file exists.
Previously, short sessions without a persisted file would silently
skip the hook. Now fires with empty messages array so plugins
always know a reset occurred.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: validate bootstrap filenames and add compaction hook timeout
- Only load extra bootstrap files whose basename matches a recognized
workspace filename (AGENTS.md, TOOLS.md, etc.), preventing arbitrary
files from being injected into agent context.
- Wrap before_compaction hook in a 30-second Promise.race timeout so
misbehaving plugins cannot stall the compaction pipeline.
- Clarify hook comments: before_compaction is intentionally awaited
(plugins need messages before they're discarded) but bounded.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: make before_compaction non-blocking, add sessionFile to after_compaction
- before_compaction is now true fire-and-forget — no await, no timeout.
Plugins that need full conversation data should persist it themselves
and return quickly, or use after_compaction for async processing.
- after_compaction now includes sessionFile path so plugins can read
the full JSONL transcript asynchronously. All pre-compaction messages
are preserved on disk, eliminating the need to block compaction.
- Removes Promise.race timeout pattern that didn't actually cancel
slow hooks (just raced past them while they continued running).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: add sessionFile to before_compaction for parallel processing
The session JSONL already has all messages on disk before compaction
starts. By providing sessionFile in before_compaction, plugins can
read and extract data in parallel with the compaction LLM call rather
than waiting for after_compaction. This is the optimal path for memory
plugins that need the full conversation history.
sessionFile is also kept on after_compaction for plugins that only
need to act after compaction completes (analytics, cleanup, etc.).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor: move bootstrap extras into bundled hook
---------
Co-authored-by: Solomon Steadman <solstead@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Clawdbot <clawdbot@alfie.local>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* fix: defer gateway restart until all replies are sent
Fixes a race condition where gateway config changes (e.g., enabling
plugins via iMessage) trigger an immediate SIGUSR1 restart, killing the
iMessage RPC connection before replies are delivered.
Both restart paths (config watcher and RPC-triggered) now defer until
all queued operations, pending replies, and embedded agent runs complete
(polling every 500ms, 30s timeout). A shared emitGatewayRestart() guard
prevents double SIGUSR1 when both paths fire simultaneously.
Key changes:
- Dispatcher registry tracks active reply dispatchers globally
- markComplete() called in finally block for guaranteed cleanup
- Pre-restart deferral hook registered at gateway startup
- Centralized extractDeliveryInfo() for session key parsing
- Post-restart sentinel messages delivered directly (not via agent)
- config-patch distinguished from config-apply in sentinel kind
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: single-source gateway restart authorization
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* fix(agents): wait for agent idle before flushing pending tool results
When pi-agent-core's auto-retry mechanism handles overloaded/rate-limit
errors, it resolves waitForRetry() on assistant message receipt — before
tool execution completes in the retried agent loop. This causes the
attempt's finally block to call flushPendingToolResults() while tools
are still executing, inserting synthetic 'missing tool result' errors
and causing silent agent failures.
The fix adds a waitForIdle() call before the flush to ensure the agent's
retry loop (including tool execution) has fully completed.
Evidence from real session: tool call and synthetic error were only 53ms
apart — the tool never had a chance to execute before being flushed.
Root cause is in pi-agent-core's _resolveRetry() firing on message_end
instead of agent_end, but this workaround in OpenClaw prevents the
symptom without requiring an upstream fix.
Fixes#8643Fixes#13351
Refs #6682, #12595
* test: add tests for tool result flush race condition
Validates that:
- Real tool results are not replaced by synthetic errors when they arrive in time
- Flush correctly inserts synthetic errors for genuinely orphaned tool calls
- Flush is a no-op after real tool results have already been received
Refs #8643, #13748
* fix(agents): add waitForIdle to all flushPendingToolResults call sites
The original fix only covered the main run finally block, but there are
two additional call sites that can trigger flushPendingToolResults while
tools are still executing:
1. The catch block in attempt.ts (session setup error handler)
2. The finally block in compact.ts (compaction teardown)
Both now await agent.waitForIdle() with a 30s timeout before flushing,
matching the pattern already applied to the main finally block.
Production testing on VPS with debug logging confirmed these additional
paths can fire during sub-agent runs, producing spurious synthetic
'missing tool result' errors.
* fix(agents): centralize idle-wait flush and clear timeout handle
---------
Co-authored-by: Renue Development <dev@renuebyscience.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* feat(gateway): add register and awaitDecision methods to ExecApprovalManager
Separates registration (synchronous) from waiting (async) to allow callers
to confirm registration before the decision is made. Adds grace period for
resolved entries to prevent race conditions.
* feat(gateway): add two-phase response and waitDecision handler for exec approvals
Send immediate 'accepted' response after registration so callers can confirm
the approval ID is valid. Add exec.approval.waitDecision endpoint to wait for
decision on already-registered approvals.
* fix(exec): await approval registration before returning approval-pending
Ensures the approval ID is registered in the gateway before the tool returns.
Uses exec.approval.request with expectFinal:false for registration, then
fire-and-forget exec.approval.waitDecision for the decision phase.
Fixes#2402
* test(gateway): update exec-approval test for two-phase response
Add assertion for immediate 'accepted' response before final decision.
* test(exec): update approval-id test mocks for new two-phase flow
Mock both exec.approval.request (registration) and exec.approval.waitDecision
(decision) calls to match the new internal implementation.
* fix(lint): add cause to errors, use generics instead of type assertions
* fix(exec-approval): guard register() against duplicate IDs
* fix: remove unused timeoutMs param, guard register() against duplicates
* fix(exec-approval): throw on duplicate ID, capture entry in closure
* fix: return error on timeout, remove stale test mock branch
* fix: wrap register() in try/catch, make timeout handling consistent
* fix: update snapshot on timeout, make two-phase response opt-in
* fix: extend grace period to 15s, return 'expired' status
* fix: prevent double-resolve after timeout
* fix: make register() idempotent, capture snapshot before await
* fix(gateway): complete two-phase exec approval wiring
* fix: finalize exec approval race fix (openclaw#3357) thanks @ramin-shirali
* fix(protocol): regenerate exec approval request models (openclaw#3357) thanks @ramin-shirali
* fix(test): remove unused callCount in discord threading test
---------
Co-authored-by: rshirali <rshirali@rshirali-haga.local>
Co-authored-by: rshirali <rshirali@rshirali-haga-1.home>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
The Discord send action was going through the plugin handler path
which wasn't passing the silent flag to sendMessageDiscord.
- Add silent param reading in handle-action.ts
- Pass silent to handleDiscordAction
- Add silent param in discord-actions-messaging.ts sendMessage case
Adds support for sending Discord voice messages via the message tool
with asVoice: true parameter.
Voice messages require:
- OGG/Opus format (auto-converted if needed via ffmpeg)
- Waveform data (generated from audio samples)
- Duration in seconds
- Message flag 8192 (IS_VOICE_MESSAGE)
Implementation:
- New voice-message.ts with audio processing utilities
- getAudioDuration() using ffprobe
- generateWaveform() samples audio and creates base64 waveform
- ensureOggOpus() converts audio to required format
- sendDiscordVoiceMessage() handles 3-step Discord upload process
Usage:
message(action='send', channel='discord', target='...',
path='/path/to/audio.mp3', asVoice=true)
Note: Voice messages cannot include text content (Discord limitation)
* increase image tool maxTokens from 512 to 4096
* fix: cap image tool tokens by model capability (#11770) (thanks @detecti1)
* docs: fix changelog attribution for #11770
---------
Co-authored-by: Peter Steinberger <steipete@gmail.com>