Track bundled skill IDs in a .bundled-manifest.json file. On each
initialization, remove managed skills that were previously bundled
but are no longer present in the bundle directory. User-installed
skills are not affected since they are never recorded in the manifest.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Upgrade @mariozechner/pi-ai and @mariozechner/pi-agent-core from 0.50.3
to 0.52.9 to support latest models (claude-opus-4-6, o3, o3-mini).
Breaking type changes addressed:
- exactOptionalPropertyTypes: use conditional spread or `| undefined`
- TOOL_PROFILES removed: strip all profile references from CLI
- AgentMessage union requires timestamp: cast test fixtures
- AsyncAgent.id → sessionId
- Add explicit callback parameter types for SDK event handlers
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Clarify that local whisper is the primary provider (free, offline),
OpenAI API is the fallback, and the skill only activates when both
are unavailable. Add setup instructions noting no restart is required.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Only cache successful whisper binary lookups. When whisper is not found,
leave the cache empty so subsequent calls re-check PATH. This allows
the app to detect a newly installed whisper without requiring a restart.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace global lastRoute-based reply targeting with a FIFO route queue
(pendingRoutes) that snapshots route + ack targets at each debouncer flush.
Use agent_start/agent_end lifecycle instead of message_end to ensure stable
routing across multi-turn runs. Track per-message 👀 acks in ackBuffer →
activeAcks for precise cleanup. Two-gate typing stop: only stop when all
queued runs complete.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Previously all agent errors (including 400 invalid_request_error) were
emitted as agent_error events, triggering the UI error banner and
interrupting the chat flow. Now only auth-related errors (401, no API key)
emit agent_error so the "Configure" banner appears. Runtime errors like
400 are still shown as chat messages but don't block the agent.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
setProvider() was saving the alias-resolved provider (e.g. "anthropic"
instead of "claude-code") to session metadata. On restart, this caused
the wrong provider to be selected. Now saves the original providerId
so the exact user selection is preserved.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Move session metadata loading before provider resolution so that
the stored provider from a previous setProvider() call is used
in the fallback chain (options > session meta > credentials > default)
instead of always falling back to "kimi-coding".
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Cron reminders were silently skipped when heartbeat.md had no actionable
content. Now cron: and exec-event reasons both bypass the empty-file
guard so scheduled reminders always reach the agent.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Merge main's run mutex + soft error return with branch's refreshAuthState(),
keeping getApiKey defensive throw as defense-in-depth.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Style is now solely managed by the agent editing soul.md directly,
removing the need for UI controls, IPC handlers, and typed constants.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
getApiKey errors thrown inside PiAgentCore's internal async context
result in UnhandledPromiseRejection instead of propagating to the
caller. Return a graceful error early so AsyncAgent can emit it
through the subscriber mechanism to the UI.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Fix double useChannels() instantiation: call once in ChannelsPage,
pass as props to TelegramCard
- Mask bot tokens in channels:getConfig before sending to renderer
- Add input validation (isValidId, token length) on all IPC handlers
- Fix stopAccount() to clean up typingTimer, lastRoute, aggregator,
and debouncer when stopping the account they belong to
- Add try/catch to stopChannel/startChannel in useChannels hook
- Consistent return type { ok, error? } on channels:stop handler
- Add tooltip hint on disabled Remove button
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add setChannelAccountConfig/removeChannelAccountConfig to CredentialManager
for persisting channel tokens. Make ChannelManager.startAccount public and
add stopAccount for individual account lifecycle control via IPC.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- InboundDebouncer: batches rapid-fire messages from the same conversation
into a single agent.write() call (500ms idle, 2s hard cap)
- ACK reactions: add 👀 emoji on message receipt, remove on completion
(addReaction/removeReaction on ChannelOutboundAdapter interface)
- Grammy sequentialize middleware: ensures same-chat updates are processed
in order, preventing race conditions on shared state
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Critical:
- describe-video: add mkdir for MEDIA_CACHE_DIR before ffmpeg write
- telegram: check bot ID (not is_bot) for reply-to detection in groups
Important:
- telegram: check @mention in caption for media messages in groups
- hub: add .catch() to channelManager.startAll()
- describe-image: add 20MB file size check to prevent OOM
- async-agent: remove dead writeWithImages, refactor with enqueue()
- manager: lazy agent subscription via ensureSubscribed() to handle
late agent availability and agent replacement
Suggestions:
- telegram-format: escape quotes in link URLs to prevent HTML breakout
- transcribe: catch API errors and return null (match local fallback)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add isInsideTable() to BlockChunker: prevents breaking Markdown tables
in the middle (table rows lose header context when split across messages)
- Set Telegram chunkerConfig maxChars to 4000 (was default 2000; Telegram
API limit is 4096, leaving room for HTML formatting overhead)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add describe-image.ts: OpenAI Vision API (gpt-4o-mini) image description
- Add describe-video.ts: ffmpeg frame extraction + Vision API description
- Rewrite transcribe.ts: local whisper/whisper-cli → OpenAI API → null
- Update manager.ts routeMedia(): all media converted to text before agent
- Image: describeImage() → text (was: raw ImageContent via writeWithImages)
- Video: describeVideo() → text (was: file path info only)
- Audio: unchanged (but underlying transcribeAudio now tries local first)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Move audio transcription from agent-driven (exec + local whisper) to
Manager-layer processing via OpenAI Whisper API. Voice messages are
now transcribed automatically before the agent sees them, so the
agent only receives text. Local whisper skill remains as fallback
when API key is not configured. Also changed default model from
turbo to base for faster first-time experience.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add writeWithImages() to AsyncAgent for passing images directly to
the LLM via ImageContent. Extend Agent.run() to accept optional
images parameter. Update ChannelManager.routeIncoming() to download
media files and forward them: images as ImageContent to the LLM,
audio/video/document as file paths for agent-driven processing.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add handlers for voice, audio, photo, video, and document messages.
Each handler emits a ChannelMessage with media attachment metadata.
Implement downloadMedia() to fetch files from Telegram API and save
to the local media cache directory.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add ChannelMediaAttachment type with support for audio, image, video,
and document media types. Extend ChannelMessage with optional media
field and ChannelPlugin with optional downloadMedia method.
Add MEDIA_CACHE_DIR path for downloaded media files.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Forward voice messages and audio files to the agent as <media:audio>
placeholder text. In groups, only process voice/audio that replies to
the bot. Includes caption text if present.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add sendTyping to ChannelOutboundAdapter (optional per platform)
- Implement typing lifecycle in ChannelManager (5s interval, cleanup on message_end/error/clear)
- Convert Markdown to Telegram HTML subset (bold, italic, code, links, blockquotes)
- Fallback to plain text on HTML parse errors
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
PiAgentCore was created with an empty object when no API key was
initially configured. This broke dynamic provider switching because
setProvider() updated currentApiKey but PiAgentCore had no getApiKey
callback to read it. Always provide the callback so it dynamically
reads the current key.
Also adds AgentErrorEvent to MulticaEvent and emits it from
AsyncAgent.write() catch handlers so errors flow through the
subscriber mechanism to IPC listeners.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Each tool call added an abort listener to the shared agent signal
without cleanup, exceeding the default 10-listener limit after 11+
exec calls. Fix by using { once: true } and removing the listener
on child process close (exec) to prevent accumulation.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Instead of keeping announced runs in the registry for 60 minutes
(archive sweeper), delete them right after findings are delivered
to the parent. This prevents stale completed tasks from appearing
in sessions_list on subsequent parent turns.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
After child subagents complete, the coalesced announcement runs as an
internal turn which rolls back all messages from the parent's in-memory
context. This causes the parent LLM to lose findings in subsequent turns.
Add persistResponse option to writeInternal that re-injects the LLM's
summary as a non-internal assistant message after the internal run
completes. The internal prompt stays hidden while the summary persists
in both memory and session JSONL for future turns.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add agent_error event type to MulticaEvent union so errors from
agent runs reach subscribe() consumers (Desktop IPC + Channel).
Make emitMulticaEvent public on Runner so AsyncAgent can emit errors.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace per-conversation agent creation with single Hub agent model.
Messages from channels are routed to the existing Hub agent via
agent.write(), and replies are sent back through the lastRoute context.
Desktop and Gateway paths call clearLastRoute() so channel replies
stop when the user switches input surface.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>