- Added support for live runtime model metadata in team provisioning.
- Implemented functions to extract and manage CLI flag values for team members.
- Updated member specifications to include effective models based on provider defaults.
- Enhanced UI dialogs to check selected providers in parallel, improving responsiveness.
- Added tests for handling model unavailability during team bootstrap and launch processes.
- Introduced CLI_INSTALLER_VERIFY_PROVIDER_MODELS IPC channel for on-demand model verification.
- Implemented handler for verifying provider models in the CliInstallerService.
- Enhanced CLI installation status management with model verification state and availability.
- Updated related components to support model verification feedback in the UI.
- Updated components in the agent-graph renderer to utilize context hooks instead of the store for accessing team data.
- Introduced `useGraphActivityContext` and `useGraphMemberPopoverContext` hooks to streamline data management.
- Refactored `GraphBlockingEdgePopover`, `GraphNodePopover`, and `GraphTaskCard` components for improved performance and readability.
- Enhanced imports in `MemberDetailDialog` for better organization.
- Added stable slot layout support in various components, enhancing the layout and interaction of nodes.
- Updated TypeScript configuration to include new paths for the agent-graph package.
- Refactored layout logic in activity lanes and kanban to accommodate stable slot assignments.
- Enhanced GraphView and GraphControls to support sidebar visibility toggling and owner slot drop handling.
- Introduced new types for layout management in GraphDataPort and related files.
- Updated README to include stable slot layout documentation.
* fix(team): resolve stuck "reconciling" state and skip resume when teammates never spawned
Addresses #54.
When a team launch fails to bootstrap teammates, the team gets stuck showing
"Last launch is still reconciling" indefinitely, and retrying with --resume
reconnects the lead but does not re-spawn the dead teammates. The only
workaround was enabling "Clear context (fresh session)", which loses the
lead's prior conversation context.
Two root causes addressed:
1. createPersistedLaunchSnapshot counted members still in 'starting' state
(agentToolAccepted=false) as 'pending' regardless of launchPhase. When
launchPhase was 'finished' with never-spawned members, the aggregate
state stayed as 'partial_pending' forever, rendered as "still reconciling".
Fix: when launchPhase != 'active', promote such members to
'failed_to_start' so the aggregate becomes 'partial_failure'
("Launch failed partway"), which correctly signals a terminal state.
2. TeamProvisioningService._launchTeamInner always used --resume when a
previous leadSessionId existed, even if the previous launch had no
teammates successfully spawned. The CLI's deterministic reconnect path
restores lead context but does not re-spawn dead teammates, so the team
stays broken across relaunches. Fix: before adding --resume, read the
persisted launch state. If every expected teammate is 'starting' (never
spawned) or 'failed_to_start', skip --resume so the CLI performs a full
fresh bootstrap that spawns all teammates.
Verified manually on Linux: a team stuck in "still reconciling" correctly
transitions to "failed partway" after the first fix, and the next Launch
(without "Clear context") fully bootstraps and brings teammates online.
* fix(team): narrow skip resume to never-spawned teammates
---------
Co-authored-by: 777genius <quantjumppro@gmail.com>
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
- Add resolvedApprovals Map to store (request_id -> allowed/denied)
- Record result in respondToToolApproval and autoResolved events
- NoiseRow accepts optional icon prop
- Permission request rows show: check (approved), x (denied),
clock (pending), or faded check (pre-existing/unknown)
Move useMemo for sourceColor BEFORE the early return (if !current),
and restore teamSummary/teamColor/displayName variables that were
accidentally removed. Fixes 'Rendered fewer/more hooks' React crash.
Replace plain text teammate name with MemberBadge component showing
avatar + colored name badge, consistent with how members are displayed
in the Messages panel.
Popover:
- Shows "working on [task subject]" with spinning Loader2 when member
has currentTaskId (same pattern as CurrentTaskIndicator in MemberCard)
- Click task subject → opens TaskDetailDialog
Canvas:
- Active members with currentTaskId get subtle spinning arc around hexagon
- Spinner only shows when state is active/thinking/tool_calling
Adapter:
- Passes currentTaskId + currentTaskSubject from ResolvedTeamMember
- Looks up task subject from data.tasks
Port types:
- Added currentTaskId, currentTaskSubject to GraphNode
- Profile button dispatches 'graph:open-profile' → MemberDetailDialog
(was incorrectly mapped to 'graph:send-message')
- Double-click on member → opens profile (was: send message)
- New "Add Task" button on member popover → CreateTaskDialog with owner pre-filled
- New events: graph:open-profile, graph:create-task
- TeamDetailView listens for both new events
DRY fixes:
- GraphOverlay.tsx: 539 LOC → 85 LOC minimal fallback (was dead code)
- COLUMN_LABELS: import from COLORS instead of hardcoded hex duplicates
- Alpha hex LUT: consolidated to single source in colors.ts
- TeamGraphTab: extract typed dispatchOpenTask/dispatchSendMessage helpers
(was 8 repeated CustomEvent dispatches)
SOLID fixes:
- D-1: lucide-react added to peerDependencies in package.json
- S-1: GraphOverlay no longer has 8 responsibilities (now just 1 fallback)
Architecture:
- renderOverlay prop properly used by both Tab and Overlay
- Popover rendering lives in features/ layer only (no duplication with package)
- New: GraphNodePopover.tsx in features/agent-graph/ui/
Uses @renderer/components/ui/badge, button, agentAvatarUrl
No code duplication with MemberCard/MemberHoverCard patterns
- GraphView: added renderOverlay prop — host app injects its own popover
Falls back to built-in GraphOverlay if renderOverlay not provided
- TeamGraphTab + TeamGraphOverlay pass renderOverlay with GraphNodePopover
Member popover: avatar, status dot, role, context bar, badges, Message/Profile
Task popover: displayId, status/review badges, Open button
Process popover: label, URL link
- TeamGraphOverlay accepts onSendMessage, onOpenTaskDetail, onOpenMemberProfile
- TeamDetailView passes dialog openers to overlay:
- Message → opens SendMessageDialog with recipient pre-filled
- Open (task) → opens TaskDetailDialog with task found by ID
- Open (member) → opens SendMessageDialog for that member
- Double-click → same as Open
- Removed console.log stubs
- Member/lead nodes show robohash avatar image clipped to circle inside hex
- Async image loading with cache (fallback to first letter while loading)
- avatarUrl field added to GraphNode type + adapter passes agentAvatarUrl()
- Column headers now centered over pill center (was offset by half pill width)
1. Member spawn animation: rotating dashed ring for 'spawning' status,
pulsing hex outline for 'waiting' status
2. Edge flash: active edges pulse brighter (0.1-0.5 alpha) with glow
shadow when particles travel along them
3. Smooth task positioning: tasks LERP to target position (0.15 factor)
instead of teleporting when kanban column changes
4. Comment flight particles: when a member adds a comment to a task,
a purple particle with speech bubble flies from member to task node
relayLeadInboxMessages only processes unread messages after
provisioningComplete, but CLI marks permission_request messages as
read after native delivery -- before our relay runs.
Move permission_request inbox scan BEFORE provisioningComplete check.
Scan ALL messages (including read=true), track processed IDs via
processedPermissionRequestIds Set on ProvisioningRun to prevent
re-emitting. Also look up both alive and provisioning runs so the
scan works during team bootstrap.
Split ToolApprovalSettingsPanel into ToolApprovalSettingsToggle (inline
button) and ToolApprovalSettingsContent (full-width expandable panel).
Toggle sits in the actions row, content renders below it inside the popup.
- Add semi-transparent backdrop (bg-black/40) behind the approval popup
to draw attention and dim surrounding UI
- Refactor ToolApprovalSettingsPanel to render toggle button inline
(fragment-based) so it can sit in the actions row next to pending count
- Settings button now appears right-aligned at the same level as
Allow/Deny buttons; expanded panel renders below
- Parse raw user text for permission_request in handleStreamJsonMessage
(covers case where permission_request arrives without <teammate-message> wrapper)
- Add [PERM-TRACE] logger.warn diagnostics to trace the exact flow:
where permission_request is detected, whether it reaches handleTeammatePermissionRequest,
and whether relay or stdout interception triggers
- These logs will help diagnose why ToolApprovalSheet may not appear
relayLeadInboxMessages only runs after provisioningComplete, but
teammate permission_request messages arrive during bootstrap (before
provisioning finishes). Claude Code delivers them natively via stdout
type:"user" messages, bypassing the relay entirely.
Add permission_request interception in handleNativeTeammateUserMessage
which processes stdout user messages at all times, including during
provisioning. This ensures ToolApprovalSheet appears immediately when
teammates need tool permission.
When Claude Code runtime natively delivers permission_request to the
lead via stdout, the message was excluded from interception by the
nativeMatchedMessageIds filter. This caused the ToolApprovalSheet to
not appear for teammate requests.
Remove nativeMatchedMessageIds check from the permission_request
filter so interception works even for natively delivered messages.
When auto-approve is disabled, teammate tool requests arrived as
permission_request JSON via SendMessage and rendered as "Raw JSON"
with no way to approve/deny (#29).
- Intercept permission_request in lead inbox relay, convert to
ToolApprovalRequest and show in existing ToolApprovalSheet
- Respond via teammate inbox (permission_response) + control_response
via stdin as fallback
- Show teammate name in approval header (e.g. "bob — Bash")
- Compact noise label in Messages panel for permission_request/response
- Proper file locking, race condition protection, idempotency checks
- Added support for tracking task change presence with new IPC channels: TEAM_GET_TASK_CHANGE_PRESENCE and TEAM_SET_CHANGE_PRESENCE_TRACKING.
- Introduced JsonTaskChangePresenceRepository and TeamLogSourceTracker to manage task change presence data.
- Enhanced ChangeExtractorService to utilize task change presence services for improved task change detection.
- Updated TeamDataService to integrate task change presence tracking and resolve task change presence states.
- Modified UI components to reflect task change presence status in Kanban and task detail views.
This feature aims to provide real-time insights into task changes, enhancing user experience and task management capabilities.
Widen dialog (max-w-lg → max-w-2xl), remove prose max-width constraint
that caused empty space on the right, and filter out the Downloads
heading with everything below it from release notes.
HEAD-request the expected installer URL (DMG/EXE/AppImage) before
notifying the user about a new version. If CI hasn't finished
uploading the artifact for the current OS yet, the notification is
suppressed and retried on the next periodic check.
Extract sendUserTaskStartNotification as reusable private method.
When a task is created via UI directly in "In Progress" column
(startImmediately=true), the controller's maybeNotifyAssignedOwner
skips the lead. Now createTask sends the notification to the lead
with full description, prompt, and task_get instructions.
Add "Already logged in?" button next to Login that expands a
step-by-step troubleshooting panel: re-check status, terminal
commands, re-login instructions, and binary path display.
Also clarifies that browsing works without login.
- Added a new IPC handler for starting tasks triggered by users, ensuring that the task owner is always notified.
- Introduced `startTaskByUser` method in `TeamDataService` to handle task initiation and notifications.
- Updated relevant components and API interfaces to support the new functionality, including changes in the UI to call `startTaskByUser` instead of the previous `startTask`.
- Documented agent block usage for internal instructions in CLAUDE.md.
This enhancement improves user interaction with task management by providing a clear mechanism for user-initiated task starts.
- SearchBar: 300ms debounce with local input state, flush on Enter
- CommandPalette: global search debounce 200→400ms
- Show "500+" when search results are capped
- Fix: syncSearchMatchesWithRendered now updates searchMatchItemIds
Ported from upstream 5c7f921e (SearchBar/CommandPalette parts).
Manually ported from upstream:
- c239cda6: translateWslMountPath() converts /mnt/X/... → X:/... on Windows
- 7f5fbdab: normalizeDriveLetter() uppercases drive letter (c:/ → C:/)
Both are no-op on macOS/Linux. Fixes session fragmentation on Windows+WSL.
Manually ported from upstream:
- ef2e0868: deduplicate streaming JSONL entries by requestId
- 4f21f267: mark stale ongoing sessions as dead after 5min inactivity
- Changed references in messages to use the **Agent** tool instead of the Task tool for spawning teammates.
- Added warnings for missing team_name to ensure agents are persistent rather than ephemeral.
- Updated documentation within the code to reflect the new requirements for spawning teammates.
- Use buildEnrichedEnv() in PtyTerminalService so login terminal gets
full PATH (Homebrew, nvm, etc.) and USER for Keychain lookup
- Add cliInstaller:invalidateStatus IPC to clear cached auth status
after successful login, preventing stale "not logged in" responses
- Show "Verifying authentication..." spinner instead of flashing
the "Not logged in" banner between modal close and status refresh
Ref #27
On macOS, the Claude CLI uses a Keychain namespace derived from the
presence of CLAUDE_CONFIG_DIR env var. Setting it to the default
~/.claude creates a different Keychain key than when the var is absent,
causing "not logged in" errors even after successful `claude auth login`.
Only set CLAUDE_CONFIG_DIR when the user has configured a custom path
that differs from the auto-detected default.
Fixes#27
fs.rm() without { recursive: true } throws ERR_FS_EISDIR on directories,
unlike the deprecated fs.rmdir() which handled empty dirs. Also fix
broken roadmap bullet in README.
- useMarkCommentsRead: replace useRef().current with useCallback for
stable ref callback (accessing ref during render is not allowed)
- useAttachments: add eslint-disable for intentional ref sync pattern
- Fix incorrect error message when attaching files to team lead while team is offline
- Kanban columns: color only on headers, body with 30% alpha tint per user preference
- Worktree projects now correctly detected on Dashboard via path-based detection
- Filter raw protocol messages (idle_notification, teammate-message) from lead thoughts
- Consistent text styles in Attachments section (From original message / From comments)
- Secondary sort for teams by lastActivity timestamp with alphabetical fallback
- Remove colored background from team cards, keep only left border
- Dynamic member color in Add Members dialog based on next available palette color
- Stylized @-mentions in task comments with colored MemberBadge
- Refactor CLI env resolution to shared utility
- Rearranged roadmap items in README.md for better organization, moving planning mode and visual workflow editor to the top.
- Updated the flag icon for English in LanguageSwitcher.vue from the US to the GB flag.
- Enhanced user environment setup in CliInstallerService.ts by adding user information retrieval.
- Improved timestamp handling in MemberLogsTab.tsx to better reflect recent activity.
- Adjusted button class names in ProvisioningProgressBlock.tsx for consistency.
- markdownPlugins.ts: use dot notation for abbr attribute access
- useResizablePanel.ts: wrap ref.current assignments in useEffect
to comply with react-hooks/refs rule
Three issues prevented standalone (non-Electron) mode from working:
1. sentry.ts used a top-level `import * from '@sentry/electron/main'`
which crashes in plain Node.js. Changed to a try/catch require()
so the module is safe to import in both environments.
2. vite.standalone.config.ts resolved all paths relative to __dirname
(docker/) but is invoked from the repo root. Fixed to resolve
relative to the repo root via a ROOT constant.
3. The electron stub was missing `safeStorage` and `screen` exports
that newer code imports. Added them, and externalized
agent-teams-controller (plain CJS with relative requires that
break when bundled by Vite).
The previous Object.fromEntries spread would silently overwrite the
custom abbr attribute list if rehype-sanitize adds abbr to its default
schema in a future version. Simplify to a direct merge.
Add rehype-sanitize to the markdown plugin chain to prevent XSS via
malicious HTML in rendered content. The rehype-raw plugin was passing
inline HTML through without sanitization, allowing script injection
through tags like <img onerror=...> or <script>.
The sanitization schema extends GitHub's default allowlist with additional
tags Claude commonly uses (mark, abbr, u, figure, figcaption) while
stripping all event handlers, scripts, and dangerous attributes.
Plugin order: rehype-raw → rehype-sanitize → rehype-highlight ensures
raw HTML is parsed, then cleaned, then syntax highlighting is applied.
- Implemented syntax highlighting for command inputs in the EditToolViewer component, enhancing readability.
- Updated the renderInput function in renderHelpers to utilize the highlightLines utility for improved command display.
- Added syntaxHighlight prop to SkillReviewDialog for consistent styling across components.
- Updated the project title in README.md to include a hyperlink for easier access to the documentation.
- Removed unused media files (compact.mp4, context.png, demo.mp4, noti.mp4) from the public directory to clean up the project.
- Enhanced the preload and renderer components to include a new method for retrieving file paths, improving file handling capabilities.
- Added support for additional languages: Chinese (zh), Spanish (es), Hindi (hi), Arabic (ar), and Portuguese (pt) in the LanguageSwitcher and i18n configuration.
- Updated content files for English and Russian to clarify the free usage of the app.
- Enhanced content management by importing new language files into the content structure.
- Adjusted locale handling to ensure proper recognition of supported languages in the application.
- Added a loading progress indicator to the HeroDemoVideo component, displaying the buffered video progress.
- Updated event listeners to track video loading state and progress.
- Enhanced skeleton loading UI with a spinner and progress bar for better user feedback during video loading.
- Changed video preload attribute from "metadata" to "auto" for improved loading performance.
- Introduced baseURL configuration to dynamically set asset paths in the landing components.
- Updated AppLogo and HeroSection components to use baseURL for logo image sources.
- Refactored ScreenshotsSection to utilize a publicPath function for consistent image path handling.
- Improved LanguageSwitcher to synchronize the i18n locale with the store on mount.
- Enhanced TaskCommentInput to handle file uploads more robustly, including validation for empty files and improved error handling.
- Adjusted MessageComposer to conditionally support attachments based on team status.