P1: Poller no longer overwrites nextCursor/hasMore — those belong
to the "Load older" flow. Both poller and loadOlder now dedup
messages by messageId or timestamp+from fingerprint.
P1: Cursor is now compound (timestamp|messageId) with stable
tie-breaking sort. Messages sharing the same timestamp at page
boundaries are no longer lost.
P2: getMessagesPage now applies the same enrichment as getTeamData:
leadSessionId propagation and slash-command-result annotation.
P3: Added 3 tests for getMessagesPage covering pagination, cursor
stability with same-timestamp messages, and slash command annotation.
Two different interval sets with the same length would produce
the same cache key, returning stale results. Serialize each
interval's startedAt~completedAt into the key.
capTimerMap was cancelling oldest pending throttle/debounce timers
when the Map exceeded a size limit. Since the schedulers don't
replay dropped keys, this silently lost refresh callbacks — leaving
session/team state stale after high-volume file change events.
These Maps are self-cleaning (entries delete themselves when their
callback fires) and hold ~100 bytes per entry. Even 200 entries
is negligible memory. Removing the cap fixes the data freshness
issue without any memory concern.
Returning messages: [] broke the slash command annotation test and
any code relying on getTeamData.messages (notifications, dedup).
Keep a small batch (50 newest) in getTeamData for compatibility.
Full message history is available via getMessagesPage() API.
New cursor-based IPC endpoint for the messages timeline panel:
- team:getMessagesPage(teamName, { beforeTimestamp?, limit? })
returns { messages, nextCursor, hasMore }
- Cursor is timestamp-based — stable under live message insertion
- Default page size 50, max 200
getTeamData no longer includes messages in its response, eliminating
the ~1MB messages payload from every team refresh. Messages are now
fetched independently by MessagesPanel.
MessagesPanel changes:
- Fetches initial page on mount via getMessagesPage API
- Auto-refreshes newest page every 5s when team is alive
- "Load older messages" button for pagination
- Falls back to prop messages if API fails (graceful degradation)
The TeamDataWorkerClient logged console.warn when the worker file
was not found, which is expected during tests (no build output).
The test setup treats unexpected warnings as failures.
Downgrade to logger.debug for the "not found" message and remove
the eager warning from resolveWorkerPath().
- Move endRefreshing() outside the !cancelled guard in finally block
so it always runs even when the effect is torn down mid-refresh.
- Only call load() when isTabActive or on first load — prevents
unnecessary fetches when a hidden tab's effect re-runs.
Critical:
- findLogsForTask: call worker directly instead of wrapping in
wrapTeamHandler, so failures propagate to catch and trigger the
main-thread fallback correctly.
- TeamDataWorkerClient: scope error/exit handlers to the specific
worker instance to prevent a stale worker's exit event from
rejecting pending requests on a replacement worker (race condition).
Major:
- TeamDataWorkerClient: validate teamName and taskId before forwarding
to worker thread (input sanitization).
- team-data-worker: include status, since, and intervals length in
cache key to prevent stale results after filter changes.
- team-data-worker: move logsInFlight.delete() into .finally() so
rejected lookups don't poison the in-flight map permanently.
- MemberLogsTab: reset refreshCountRef and refreshing state in effect
cleanup to prevent the refresh indicator from latching on permanently
when the effect tears down mid-refresh (e.g. on tab switch).
The user inbox (user.json) contains real teammate-to-user messages
generated by Claude Code CLI. Filtering it as a system inbox was
incorrect — it broke message aggregation for user-directed messages.
Only the broadcast inbox (*.json) needs to be excluded since '*'
is not a valid member name and causes a phantom member in the UI.
The broadcast inbox file (inboxes/*.json) was being parsed by
listInboxNames() as a member named "*", which appeared in the UI
as a phantom team member. Since "*" fails the MEMBER_NAME_PATTERN
validation, it could not be removed through the UI.
Filter system inbox names (*, user) from listInboxNames() so they
are not treated as real team members.
Main process — worker thread for team data:
- New team-data-worker thread handles getTeamData and findLogsForTask,
isolating heavy file I/O (scanning 300+ subagent JSONL files) from
Electron's main event loop. getTeamData dropped from ~2000ms on the
main thread to ~110ms via the worker.
- Worker-side dedup and 10s result cache for findLogsForTask prevents
redundant scans when the same task is queried multiple times.
- Discovery cache TTL raised from 5s to 30s — avoids re-scanning the
entire project directory on every call.
- Message cap at 200 in TeamDataService to keep IPC payloads under 1MB
(was sending 2200+ messages / ~3MB, stalling Chromium IPC serialization).
- IPC handlers fall back to main-thread execution if the worker is
unavailable (graceful degradation).
Renderer — useShallow and memoization (55 files):
- Added useShallow to store selectors across 55 renderer files. Batched
individual useStore() calls (e.g. 17 calls in ExtensionStoreView,
10 in ConnectionSection) into single useShallow selectors, cutting
unnecessary re-render checks on every store update.
- MemberLogsTab: three 5-second polling intervals now pause when the
parent tab is hidden (display:none). Previously 5 hidden tabs × 3
intervals = 15 polling timers firing continuously.
- KanbanColumn wrapped in React.memo to skip re-renders when props
haven't changed.
- MemberList: memoized activeMembers/removedMembers/colorMap; replaced
O(n×m) per-member task scan with a pre-computed reviewer map.
- Bounded timer Maps in store initialization to prevent unbounded growth
of debounce/throttle tracking maps during long sessions.
- Eliminated the enrichMemberBranches method from TeamDataService to simplify member branch enrichment logic.
- Updated TeamDetailView to utilize live branch tracking for both lead and member worktrees, enhancing the accuracy of displayed member branches.
- Adjusted various references to ensure membersWithLiveBranches is used consistently across the component.
- Enhanced text processing to clean up raw task ID hashes and replace pipe separators with dashes.
- Ensured consistent formatting for comments and inbox messages.
- Cross-team messages now show ghost nodes (dashed hexagons) for external teams
- Ghost nodes have purple color, link icon, and connect to lead via message edge
- Particles flow between ghost node and lead with cross-team message labels
- Cross-team popover shows external team name
- Task click opens full KanbanTaskCard with glow effects and action buttons
- All kanban task actions wired through CustomEvent to TeamDetailView
- Updated task opacity logic to simplify conditions.
- Added comment count and unread count badges to task pills for better visibility.
- Improved layout for unassigned tasks, including a section header and overflow badge.
- Enhanced task interaction by restricting drag functionality to member and lead nodes only.
- Introduced new task action event listeners for better task management in the UI.
- Preserved known task change presence across refreshes to maintain state consistency.
seedTeammateOperationalPermissionRules already pre-writes MCP tool
rules to settings.local.json before spawning the CLI. But standard
file tools (Write, Edit, NotebookEdit) were missing.
FACT: Teammates requesting Write get setMode: acceptEdits suggestion
but we can't change subprocess session mode. Pre-seeding these tools
as allow rules prevents the permission prompt entirely.
Belt-and-suspenders approach:
1. Settings file: handles all FUTURE calls (teammate finds rule on retry)
2. control_response via stdin: may unblock CURRENT waiting prompt
(now includes updatedInput: {} which was the previous ZodError fix)
Without #2, approved teammates stay stuck until team restart because
the CLI doesn't hot-reload settings.local.json for pending prompts.
FACT: Write/Edit permission_requests have permission_suggestions with
type "setMode" (not "addRules"): { type: "setMode", mode: "acceptEdits" }
Our code only handled "addRules", so Write/Edit approvals were no-ops.
Translate setMode suggestions to settings rules:
- acceptEdits → add Edit, Write, NotebookEdit to allow list
- bypassPermissions → add all common tools to allow list
- Replaced inline drawing logic for task comments with a new `drawCommentBubble` function to enhance readability and maintainability.
- The new function encapsulates the drawing of a speech-bubble icon, including the rounded rectangle body, tail, and inner dots to suggest text.
Particle direction:
- Added `reverse` flag to GraphParticle — when true, particle flies
from target → source (reverse of edge direction)
- Messages FROM teammate TO lead now fly member→lead (was lead→member)
- draw-particles.ts swaps from/to nodes when reverse=true
Reverted system message filter:
- Removed #isSystemMessage — all messages shown as particles again
(user wants to see idle_notification etc.)
- Added mcpToolCatalog to the agent-teams-controller, exporting new types and constants for MCP tool groups and names.
- Updated tools registration to utilize AGENT_TEAMS_MCP_TOOL_GROUPS for streamlined tool management.
- Enhanced tests to validate the new operational permissions and ensure correct tool registration behavior.
When user approves any mcp__agent-teams__* tool, also add all other
agent-teams tools to settings.local.json preemptively. This prevents
teammates from getting stuck on subsequent tool calls (task_get,
task_start, task_complete, etc.) since each generates a separate
permission_request and the teammate blocks until resolved.
FACT: Settings file approach only prevents FUTURE blocks, not current
ones. Pre-adding all tools on first approval covers the common case.
Remove tool description from permission_request noise label - tool name
alone is clear enough. MCP tools have long technical descriptions that
flood the Messages panel.
- Added checks to ensure that only newer versions are installed during the update process.
- Updated the notification logic to suppress alerts for non-newer updates.
- Introduced a new method to compare version numbers, improving version management in the UpdaterService.
- Enhanced the release workflow by removing unnecessary file uploads and adding canonical updater metadata publishing for better asset management.
Claude Code CLI Zod schema requires updatedInput to be a record
when behavior is 'allow'. Without it, MCP tool approvals fail with
'Tool permission request failed: ZodError: expected record, received
undefined'.
Add empty updatedInput: {} to all allow responses (autoAllow,
timeout-allow, and manual allow).
Inbox files persist across team runs. Permission_request messages from
a previous run (e.g. when team was launched without auto-approve) were
being reprocessed on the next launch, showing false ToolApprovalSheet
popups even when the new run has bypassPermissions enabled.
Filter by timestamp: skip permission_request messages older than
run.startedAt.
Allow all button and ToolApprovalSettingsPanel now use current
approval's teamName instead of selectedTeamName. Prevents applying
settings to wrong team when approval comes from a different team
than the one currently viewed.
Tool approval settings (autoAllowAll, autoAllowFileEdits, etc.) are
now stored per-team instead of globally. Clicking 'Allow all' in one
team no longer affects other teams.
- localStorage key: 'team:toolApprovalSettings:{teamName}'
- Settings loaded on team select, initialized on create/launch
- skipPermissions=false -> defaults (autoAllowAll:false)
- skipPermissions=true -> autoAllowAll:true for that team
- Main process: Map<teamName, settings> instead of single instance
- IPC: teamName parameter added to updateToolApprovalSettings
When the user previously clicked 'Allow all', autoAllowAll persists
globally in localStorage. New teams launched with skipPermissions=false
(manual tool approval) would silently auto-approve everything.
Now createTeam/launchTeam reset autoAllowAll to false when
skipPermissions is explicitly false, ensuring the ToolApprovalSheet
appears for the new team.
Split setTracking(enabled) into separate enableTracking/disableTracking
public methods per sonarjs/no-selector-parameter rule. Update all callers
and test mocks accordingly.
- Simplify LAUNCH_RE by pre-stripping optional (resume) prefix to bring
regex complexity from 23 to within the allowed 20
- Use RegExp.exec() instead of String.match() per lint rules
- Use path.normalize() in BranchStatusService tests for cross-platform
compatibility (fixes \repo vs /repo on Windows)
- Added logic to remove generated MCP config files when team creation or launch fails due to spawn errors.
- Updated tests to verify that MCP config files are correctly removed in failure scenarios, ensuring no leftover configurations persist.
- Enhanced error handling in the TeamProvisioningService to maintain clean state during provisioning operations.
MCP config files were stored in os.tmpdir() with no cleanup, and
the MCP server ran from the AppImage FUSE mount path which could
be inaccessible to Claude CLI subprocesses on Linux (fixes#31).
Changes:
- Store MCP configs in app.getPath('userData')/mcp-configs/ with
pid+timestamp filenames for safe multi-instance operation
- Copy MCP server to userData/mcp-server/<version>/ via atomic
temp-dir + rename (eliminates FUSE mount dependency)
- Add per-run cleanup in cleanupRun(), startup stale GC (7d TTL),
and shutdown own-pid GC
- Add --mcp-config recovery in respawnAfterAuthFailure()
- Add setAppDataBasePath(null) reset for test isolation
- Introduced 'task_comment_notification' message kind to enhance message handling in the team services.
- Updated TeamDataService, TeamInboxReader, and TeamSentMessagesStore to accommodate the new message kind.
- Modified filtering logic to exclude task comment notifications from the displayed messages.
- Added tests to ensure correct handling and filtering of task comment notifications.
- Replaced direct access to session.firstMessage with formatSessionLabel for consistent label formatting across components.
- Updated SessionItem, TeamSessionsSection, and KanbanFilterPopover to utilize the new formatting function.
- Enhanced display logic in SessionItem to differentiate between regular and team sessions, improving user experience.
- Added new icons for team sessions and adjusted metadata display for better clarity.
- Added a settings toggle to the GraphControls for better user interaction.
- Implemented event listeners to close settings when clicking outside or pressing Escape.
- Improved zoom controls to mark user interaction, preventing auto-fit adjustments during user actions.
- Refactored GraphOverlay to streamline rendering and position updates for selected nodes.
- Updated GraphView to utilize new layout effects for better performance and responsiveness.
- Added visual differentiation for 'task_comment' particles, adjusting size and glow effects.
- Updated drawing functions to handle new particle kind, ensuring proper rendering in the graph.
- Introduced a merge function for particles to prevent duplicates during state updates.
- Enhanced color constants for better visual representation of different particle types.
Restored markInboxMessagesRead for permission_request messages in the
early scan. Without this, processed permission_request messages stayed
read=false forever, potentially showing stale unread indicators in UI.
Root cause: handleTeammatePermissionRequest was called from 3 paths
(early inbox scan, Category 4 relay scan, stdout/native) but only
the early scan checked processedPermissionRequestIds, causing
duplicate ToolApprovalRequests and extra permission_responses.
Fix:
- Move processedPermissionRequestIds check INTO handleTeammatePermissionRequest
so ALL callers are protected by the same dedup gate
- Remove duplicate Category 4 scan that re-processed inbox messages
(early scan already covers all messages including read=true)
- Category 4 now only builds a filter Set to exclude permission_request
from relay to lead
Context window fix:
- Derive initial contextWindow from model selection (haiku=200K, else=1M)
- Use limitContext flag from request
- Updated to exact value from modelUsage on result.success
- Formula: input_tokens + cache_creation + cache_read (all three needed)
TokenUsageDisplay:
- New contextWindowSize prop → shows "X% of context" (not "X% of input")
Graph improvements:
- Pending approval: pulsing amber ring on member nodes
- Working spinner: spinning arc when member has active task
- Current task indicator in popover (Loader2 + "working on" + task name)
- GraphNode: added currentTaskId, currentTaskSubject, pendingApproval, activeTool fields
- Adapter: passes pendingApprovalAgents + currentTaskId from store
- Option keys now include question index prefix ("qi:label") to prevent
cross-question collisions when multiple questions share option labels
- Single-select clears only options from the same question, not all
- answersMessage built as per-question JSON map instead of flat join
- Backend parses JSON answers and maps to correct questions
- Fallback label "Option N" for options with undefined label
- Enter key now respects isAskQuestion selection requirement
- Selected option labels sent as message to respondToToolApproval
- control_response includes updatedInput with answers for AskUserQuestion
- useCallback/useEffect deps updated for selectedOptions and hasSelection
- Options are clickable: single-select (radio) and multi-select (checkbox)
- Selected options highlighted with green border and filled indicator
- Submit button replaces Allow for AskUserQuestion - disabled until
at least one option is selected
- Human-readable tool display names (AskUserQuestion -> Question, etc.)
- Selection resets when approval changes
Move useMemo/variable declarations BEFORE the AskUserQuestion early
return to ensure hooks are called in consistent order regardless of
which tool type is being displayed.
Instead of showing raw JSON for AskUserQuestion, render each question
with header badge, question text, and options list showing labels and
descriptions. Supports multiSelect indicator (checkbox vs radio style).
Add MessageCircleQuestion icon for the tool.
- Add pendingApproval field to GraphNode type
- Pass pendingApprovalAgents Set from store through adapter
- Draw pulsing amber ring + subtle glow on agent nodes that have
pending tool approval requests
- Include approval state in adapter cache hash for reactivity