Skip to content

v0.10.3 — parallel in-app conversations + cron-leak fix

Choose a tag to compare

@denberek denberek released this 11 May 20:09

0.10.3 — 2026-05-11

One feature, one fix. Both surfaced by real usage.

Parallel in-app conversations per agent

Hold N independent chats with the same agent in the Obsidian chat panel — different tasks, different contexts, switchable from a dropdown. Reuses the conversation plumbing that already powers Slack multi-thread.

  • Same-agent dedup is now (agent, conversationId) dedup. Two tabs on the same agent are fine; two tabs on the same (agent, conversation) reveals the existing tab.
  • A conversation picker row appears below the agent dropdown whenever the agent has more than one conversation. Single-conversation users see no UI change.
  • + New Chat creates a new conversation alongside the current one (was: wipe-the-singleton). Rename / Delete buttons next to the picker; the legacy "Main chat" is delete-protected so pre-feature data can't be orphaned accidentally.
  • Tab title becomes Chat: <agent> · <conversation name> once a second conversation exists.

Data layout mirrors Slack sessions:

  • Folder agents: <agent-folder>/conversations/<convId>.json
  • Flat agents: _fleet/memory/<agent>-conversations/<convId>.json
  • Sentinel id "default" maps back to the legacy per-agent chat.json so existing data is untouched. The picker surfaces it as "Main chat".

State persistence: AgentChatView.getState() now returns {agentName, conversationId}. Reload restores the exact same tab → same conversation. Older state files without conversationId fall back to "default".

Engine unchanged: ChatSessionOptions.inAppConversationId joins existing channelName / conversationId / threadAnchorId knobs; getChatFilePath() grows one parallel branch. The Claude --resume flow, sendMessage lock, watchdog, threading, and stats all carry through.

Fix: stop old runtime's crons on saveSettings rebuild

saveSettings() was replacing this.runtime with a fresh FleetRuntime without stopping the outgoing runtime's heartbeat / task-scheduler crons. Croner timers own their own callbacks once started — dropping the reference doesn't stop them, so each leaked runtime kept firing the schedules it was registered with for the rest of the Obsidian session.

Concrete symptom: a user changes an agent's heartbeat from "every 2h" to "every 6h", and the 2h schedule keeps firing. Even though the agent-edit path correctly re-registered on the current runtime, any prior saveSettings() (toggling status bar, editing channel credentials, etc.) had leaked a previous runtime still holding a 2h cron. Multiple leaked runtimes accumulated across a session, causing duplicate runs and making schedule edits appear not to take effect until full reload.

  • TaskScheduler.shutdown() — stop every job, clear the map, drop the pending queue.
  • FleetRuntime.shutdown() — stop heartbeat crons + delegate to scheduler. Idempotent.
  • main.saveSettings() calls runtime.shutdown() before reassigning.
  • main.onunload() calls runtime.shutdown() so "Disable plugin" without a full reload also tears down cleanly.

Regression test added on TaskScheduler — verifies post-shutdown resumeAll doesn't flush queued runs and no cron remains to fire.

Tests

108/108 green (up from 103): 4 new conversation-path tests on ChatSession + 1 scheduler-shutdown regression.

See v0.10.0 for the major Wiki Keeper / permissions release this patches.