The closing fence regex was not checking for an empty info string,
allowing e.g. ```python to incorrectly close an open fence. Also
adds missing test for tool_execution_update passthrough.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Message aggregation should not be managed by the Hub. The aggregator
is a standalone utility for client adapters to use internally — the
Hub stays clean and only does event forwarding. Third-party messaging
integrations (Discord, Telegram) will use MessageAggregator in their
own adapter layer.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Return latest messages by default instead of oldest. Support paginated
loading of older messages when scrolling up via IntersectionObserver,
with scrollHeight compensation to preserve scroll position.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add enableAggregation/disableAggregation methods to Hub for per-agent
aggregation control. When enabled, streaming text deltas are buffered
and emitted as block-reply events instead of raw stream events. The
existing streaming mode remains the default for own clients.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
BlockChunker splits streaming text at natural boundaries (paragraph,
newline, sentence, word) with configurable min/max chars and markdown
fence-safe splitting. MessageAggregator consumes AgentEvents, buffers
text deltas, and emits complete BlockReply objects via callbacks.
Enables future third-party messaging integrations (Discord, Telegram)
that cannot consume raw streaming deltas.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Sign each search request with HMAC-SHA256 using Hub ID, a per-request
nonce (UUIDv7), and unix timestamp. The signed reqId is sent in the
request body to prevent unauthorized API usage.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add localApprovalHandlers map so exec approval requests can be routed
to the Electron renderer via IPC instead of requiring a Gateway
connection. Expose setLocalApprovalHandler/removeLocalApprovalHandler
and resolveExecApproval on Hub for the IPC layer to use.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Rename CopilotHub references to Devv Search (constants, types, function
names, error message). Remove unused resolveTimeoutSeconds/resolveCacheTtlMs
imports and use constants directly.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Remove "requires API key" wording and rebrand to Devv Search across
tool definition, desktop UI, system prompt, and README.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Remove Brave and Perplexity providers in favor of a single CopilotHub
search endpoint (api-dev.copilothub.ai/web-search). Simplifies schema
to query-only, removes credential dependencies, retains caching.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Keep needsCompaction() method from compaction-feedback PR
- Keep tool result pruning from this branch
- Add "pruning" to CompactionEndEvent.reason type in events.ts
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Adding explicit return type to help TypeScript resolve the type
correctly across different build configurations.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The CompactionResult type includes "pruning" as a reason but SessionEntry
did not, causing type errors when writing compaction entries.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Two-phase pruning approach based on OpenClaw's microcompact-style:
- Soft Trim (30% utilization): Keep head 1500 + tail 1500 chars of large tool results
- Hard Clear (50% utilization): Replace old tool results with placeholder
Protections:
- Never prunes before first real user message (bootstrap protection)
- Protects last 3 assistant messages and their corresponding tool results
- Skips image-containing tool results
- Respects tool allow/deny lists
Enabled by default in tokens/summary compaction modes.
- Wrap maybeCompact() in try/catch to ensure compaction_end always fires
- Widen multicaListeners type to match subscribeAll() callback signature
- Import CompactionEndEvent from SDK instead of inline type casts
- Add doc comment explaining reason field type difference (agent vs SDK)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Profile: add usage hint for base path
- Memory Recall: clear step-by-step instructions for search and update
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Profile: concise directory path with single usage hint
- Memory Recall: one-liner instruction matching OpenClaw style
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Implements a simple memory_search tool for searching memory files:
- Searches memory.md and memory/*.md files by keyword
- Returns matching lines with context (2 lines before/after)
- Supports case-sensitive/insensitive search
- Respects maxResults limit
Tool is only available when a profile is active (has profileDir).
System prompt includes memory usage guidance when tool is present.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Forward compaction_start/compaction_end events through Hub (Gateway path)
and Desktop IPC (local path) to the Zustand messages store. Adds
CompactionEvent types to the SDK, compacting/lastCompaction state to
useMessagesStore, and event routing in both connection-store and
use-local-chat.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Introduce MulticaEvent type system parallel to pi-agent-core's AgentEvent,
with compaction_start and compaction_end events. Agent.subscribeAll() merges
both event streams. maybeCompact() now emits events bracketing the compaction
work, gated by a new SessionManager.needsCompaction() pre-check.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Memory is now managed through profile files (memory.md, memory/*.md) using
standard read/edit tools, following OpenClaw's file-first approach.
Changes:
- Remove memory/ folder with KV-based memory tools
- Remove group:memory from tool groups
- Update system prompt to remove memory tool references
- Update README docs to reflect file-based memory approach
Agents use workspace.md instructions to manage memory via file operations.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Log storage write errors in SessionManager.enqueue() instead of
silently swallowing them
- Remove existsSync check in ensureSessionDir to avoid TOCTOU race;
mkdirSync with recursive is idempotent when dir already exists
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The subagent announce flow reads the child's session file to extract
results, but saveMessage() is fire-and-forget (void enqueue). When
agent.run() completes, pending appendEntry writes may not have flushed
to disk yet, causing readLatestAssistantReply() to find no entries.
Add SessionManager.flush() and call it in AsyncAgent.write() after
agent.run() returns. This ensures waitForIdle() won't resolve until
all session data is on disk, fixing the "no return content" issue.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The mkdir call with { recursive: true } can throw ENOENT on macOS APFS
due to filesystem race conditions. This caused SpawnSession child agents
to silently fail — the session data was never written, so the parent
read back empty results ("子任务完成了,但没有返回内容").
Two fixes:
- Add retry logic to mkdir calls in acquireSessionWriteLock and
ensureSessionDir to handle transient ENOENT
- Add .catch() to SessionManager.enqueue() to prevent unhandled
promise rejections from fire-and-forget storage operations
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add a "verifying" connection state between "connected" and "registered"
so collaborators see clear feedback while waiting for the device owner
to approve their connection on Desktop.
Changes across the stack:
- Hub: verify RPC returns isNewDevice flag to distinguish new vs whitelisted
- SDK: emit "verifying" state before verify RPC, pass isNewDevice through
- Store: capture isNewDevice via onVerified, capture rejection via onError
- UI: ConnectionStatus (waiting), RejectedStatus (declined), and
verify success overlay (approved) replace the stuck scanner screen
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Update workspace.md template to prevent agents from claiming to
"remember" things without actually editing files. The new guidance:
- Uses "No Mental Notes!" as a strong warning
- Lists specific trigger phrases (记住, Remember, I prefer, etc.)
- Maps each trigger to the appropriate file to edit
- Explicitly forbids saying "I'll remember" without file edits
This addresses the issue where agents would acknowledge user
preferences verbally but not persist them to profile files.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Change `multica dev` default from gateway+console+web to desktop app
- Remove console from dev command options (use embedded Hub in desktop)
- Update package.json scripts to reflect new defaults
- Update README.md and CLAUDE.md architecture documentation
The desktop app has an embedded Hub, so no separate console/gateway is
needed for local development. Gateway is now optional, only needed for
remote client access.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Simplify 4-layer policy to 3-layer:
- Layer 1: Global allow/deny (user config)
- Layer 2: Provider-specific rules
- Layer 3: Subagent restrictions
Removed:
- ToolProfileId type (minimal/coding/web/full)
- TOOL_PROFILES constant
- getProfilePolicy function
- profile field from ToolsConfig
Users can achieve the same effect using allow/deny with group:* syntax.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Resolve tools before building subagent system prompt so the
"## Tooling" section is included, matching OpenClaw's pattern.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Only inject workspace.md into system prompt (not soul, user, memory)
- Add profile directory path to workspace section for on-demand file access
- Agent now reads soul.md, user.md, memory.md on first session using tools
- Reduces system prompt size and improves token efficiency
- Aligns with workspace.md template instructions ("Read soul.md first")
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add getProviderInfo() and setProvider() methods to Agent class
- Expose provider methods via AsyncAgent
- Add setLlmProviderOAuthToken() for storing OAuth credentials
- Extend ProviderConfig type with OAuth fields (oauthToken, oauthRefreshToken, oauthExpiresAt)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The createExecApprovalCallback was using profileId as the agentId for
approval requests, but agentSenders map is keyed by agent.sessionId.
This caused sendToClient lookups to fail, silently denying all Hub-mode
approvals. Now generates sessionId upfront and passes it separately
from profileId to the callback.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- ExecApprovalManager: tracks pending approvals, sends to clients via
Gateway, resolves on RPC response, auto-denies on timeout (fail-closed)
- RPC handler: resolveExecApproval for client decision delivery
- Hub integration: creates approval callback per agent, injects into
AsyncAgent, registers RPC handler, cancels pending on agent close
- Reads/writes exec approval config and allowlist from agent profile
- Test coverage for manager: request/resolve, timeout, cancel, errors
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add optional onApprovalNeeded callback to exec tool (backward compatible)
- Thread callback through CreateToolsOptions → AgentOptions → resolveTools
- Add ExecApprovalConfig to ProfileConfig for per-profile configuration
- Create CLI terminal approval callback (readline-based) for non-Hub mode
- Export all exec approval types and functions from tools index
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add getMessages() to Agent and AsyncAgent for retrieving session history
- Fix type annotations in hub.ts for AgentMessage handling
- Remove duplicate type export in preload.ts
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add AsyncAgent.subscribe() that allows multiple subscribers to receive
the same agent events, enabling local IPC chat to coexist with other
event consumers.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>