diff --git a/agent-teams-controller/src/internal/tasks.js b/agent-teams-controller/src/internal/tasks.js index f245664e..fb27ca12 100644 --- a/agent-teams-controller/src/internal/tasks.js +++ b/agent-teams-controller/src/internal/tasks.js @@ -392,10 +392,12 @@ function buildMemberTaskProtocol(teamName) { - Do NOT start multiple tasks at once unless the team lead explicitly directs parallel work. 3. Use MCP tool task_complete BEFORE sending your final reply: { teamName: "${teamName}", taskId: "" } + - CRITICAL: Before calling task_complete, you MUST post a task comment with your results (findings, research report, analysis, code changes summary, or any deliverable). The task comment is the primary delivery channel — the user reads results on the task board. A SendMessage to the lead is NOT a substitute: direct messages are ephemeral and not visible on the board. If you only SendMessage without a task comment, the user will never see your work. - If a new task comment means you must do more real work on that same task, FIRST add a short task comment saying what you are going to do, THEN run task_start again before doing the follow-up work. - After that follow-up work finishes, add a short task comment with the result, what changed, or what you verified. - After that, run task_complete again before your reply. - Never do comment-driven implementation/fix work while the task is still shown as pending, review, completed, or approved. + - After task_complete, send a notification to your team lead via SendMessage that includes: (a) which task is done (#), (b) a brief summary of the outcome/key findings (2-4 sentences — enough to understand the gist without reading the full comment), (c) mention that the full detailed results are in the task comment (include the commentId returned by task_add_comment so the lead can reference it, e.g. "Full report in task comment "), (d) what you will do next. Do NOT duplicate the entire results — keep it concise. - After task_complete, if the task needs review AND the team has a member whose role includes reviewing (e.g. "reviewer", "tech-lead", "qa"), IMMEDIATELY call review_request to move it to the review column and notify the reviewer: { teamName: "${teamName}", taskId: "", from: "", reviewer: "" } Do NOT leave a completed task without sending it to review when review is expected and a reviewer exists. @@ -546,6 +548,8 @@ async function memberBriefing(context, memberName) { `Member briefing for ${requestedMemberName} on team "${context.teamName}" (${context.teamName}).`, `Role: ${role}.`, `CRITICAL: If a task gets a new comment and you are going to do additional implementation/fix/follow-up work on that same task, FIRST leave a short task comment saying what you are about to do, THEN move it to in_progress with task_start, THEN do the work, and when finished leave a short result comment and move it to done with task_complete. Never skip this comment -> reopen -> work -> comment -> done cycle.`, + `CRITICAL: When you finish a task, your results (findings, research report, analysis, code changes summary, or any deliverable) MUST be posted as a task comment BEFORE calling task_complete. The task comment is the primary delivery channel — the user reads results on the task board. A SendMessage to the lead is NOT a substitute: direct messages are ephemeral and not visible on the board. If you only SendMessage without a task comment, the user will never see your work.`, + `After task_complete, send a notification to your team lead via SendMessage: include which task is done (#), a brief summary of the outcome/key findings (2-4 sentences), mention that full details are in the task comment (include the commentId from the task_add_comment response), and what you will do next. Do NOT duplicate the entire results — keep it concise.`, `CRITICAL: A newly assigned task must NOT remain silently pending/TODO. If you are idle and the task is ready to start, start it now. If it must wait because you are already finishing another task, blocked, or still need more context, leave a short task comment on the waiting task immediately with the reason and your best ETA or what you are waiting on, keep it in pending/TODO, and only move it to in_progress with task_start when you truly begin.`, `Team lead: ${leadName}.`, buildMemberLanguageInstruction(config), diff --git a/src/main/services/team/TeamProvisioningService.ts b/src/main/services/team/TeamProvisioningService.ts index 07dfc7b3..47b33116 100644 --- a/src/main/services/team/TeamProvisioningService.ts +++ b/src/main/services/team/TeamProvisioningService.ts @@ -112,7 +112,7 @@ const PREFLIGHT_AUTH_MAX_RETRIES = 2; const FS_MONITOR_POLL_MS = 2000; const TASK_WAIT_FALLBACK_MS = 15_000; const STALL_CHECK_INTERVAL_MS = 10_000; -const STALL_WARNING_THRESHOLD_MS = 45_000; +const STALL_WARNING_THRESHOLD_MS = 20_000; const STALL_WARNING_REPEAT_MS = 30_000; const TEAM_JSON_READ_TIMEOUT_MS = 5_000; const TEAM_CONFIG_MAX_BYTES = 10 * 1024 * 1024; @@ -453,7 +453,9 @@ After member_briefing succeeds: - If a newly assigned task cannot be started immediately because you are still busy on another task, leave a short task comment on that waiting task right away with the reason and your best ETA, keep it in pending/TODO, and only move it to in_progress with task_start when you truly begin. - CRITICAL: If someone comments on your task, you MUST reply on that same task via task_add_comment. Never leave a user/lead/teammate task comment unanswered, even if the reply is only a short acknowledgement or status update. Do NOT treat status changes or direct messages as a substitute for an on-task reply. - CRITICAL: If a task gets a new comment and you are going to do additional implementation/fix/follow-up work on that same task, FIRST leave a short task comment saying what you are about to do, THEN move it to in_progress with task_start, THEN do the work, and when finished leave a short result comment and move it to done with task_complete. Never skip this comment -> reopen -> work -> comment -> done cycle. -- Direct messages to your team lead are only for urgent attention, no-task situations, or when the lead explicitly asked for a direct reply. +- CRITICAL: When you finish a task, your results (findings, research report, analysis, code changes summary, or any deliverable) MUST be posted as a task comment BEFORE calling task_complete. The task comment is the primary delivery channel — the user reads results on the task board. A SendMessage to the lead is NOT a substitute: direct messages are ephemeral and not visible on the board. If you only SendMessage without a task comment, the user will never see your work. +- After task_complete, send a notification to your team lead via SendMessage that includes: (a) which task is done (#), (b) a brief summary of the outcome/key findings (2-4 sentences — enough to understand the gist without reading the full comment), (c) mention that full details are in the task comment (include the commentId returned by task_add_comment, e.g. "Full report in task comment "), (d) what you will do next. Do NOT duplicate the entire results — keep it concise. +- Beyond task-completion pings, direct messages to your team lead are only for urgent attention, no-task situations, or when the lead explicitly asked for a direct reply. - If a task-scoped update is already recorded in a task comment, do NOT send a duplicate SendMessage to the lead with the same content unless you need urgent non-task attention. When skipping a message, stay silent — never output meta-commentary about skipped or already-delivered messages. ${buildTeammateAgentBlockReminder()} ${actionModeProtocol}`; @@ -501,7 +503,9 @@ ${actionModeProtocol} - If you are the one about to do the implementation/fixes and the owner is missing or someone else, run task_set_owner to yourself immediately before task_start. - Only then run task_start when you truly begin. - If a task gets a new comment and you are going to do additional implementation/fix/follow-up work on it, FIRST leave a short task comment saying what you are about to do, THEN run task_start, then do the work, and when finished leave a short result comment and run task_complete again. Never skip this comment -> reopen -> work -> comment -> done cycle. - - Direct messages to your team lead are only for urgent attention, no-task situations, or when the lead explicitly asked for a direct reply. + - CRITICAL: When you finish a task, your results (findings, research report, analysis, code changes summary, or any deliverable) MUST be posted as a task comment BEFORE calling task_complete. The task comment is the primary delivery channel — the user reads results on the task board. A SendMessage to the lead is NOT a substitute: direct messages are ephemeral and not visible on the board. If you only SendMessage without a task comment, the user will never see your work. + - After task_complete, send a notification to your team lead via SendMessage that includes: (a) which task is done (#), (b) a brief summary of the outcome/key findings (2-4 sentences — enough to understand the gist without reading the full comment), (c) mention that full details are in the task comment (include the commentId returned by task_add_comment, e.g. "Full report in task comment "), (d) what you will do next. Do NOT duplicate the entire results — keep it concise. + - Beyond task-completion pings, direct messages to your team lead are only for urgent attention, no-task situations, or when the lead explicitly asked for a direct reply. - If a task-scoped update is already recorded in a task comment, do NOT send a duplicate SendMessage to the lead with the same content unless you need urgent non-task attention. When skipping a message, stay silent — never output meta-commentary about skipped or already-delivered messages. - If you have no tasks, wait for new assignments.`; } @@ -551,6 +555,7 @@ function buildTeamCtlOpsInstructions(teamName: string, leadName: string): string `- If you assign work to a teammate who already has another in_progress task, create/keep the newly assigned task in pending/TODO. Do NOT move it to in_progress on their behalf before they actually start.`, `- Never bulk-move many tasks at the end of a session — update status incrementally as you work.`, `- Record meaningful progress, decisions, and blockers as task comments so context is preserved on the board.`, + `- CRITICAL: Task results (findings, reports, analysis, code changes) MUST be posted as task comments — the user reads results on the task board. Direct messages alone are not visible on the board and the user will miss them.`, ``, `Parallelization guideline (IMPORTANT):`, `- If a task is genuinely parallelizable, split it into multiple smaller tasks owned by different members.`, @@ -958,7 +963,7 @@ function buildLaunchPrompt( - BEFORE doing any work on a task: mark it started (in_progress). - Immediately SendMessage "user" that you started task # (what you're doing + next step). - While working: after each meaningful milestone/decision/blocker, add a task comment on #. If the milestone is user-relevant, also SendMessage "user". - - On completion: add a final task comment (what changed + how to verify), mark the task completed, then SendMessage "user" that task # is complete and what you will do next. + - On completion: add a final task comment with your full results (findings, report, analysis, code changes summary, or any deliverable), mark the task completed, then SendMessage "user" with a brief summary of the outcome (2-4 sentences) and mention that full details are in the task comment (include the commentId from the task_add_comment response). The task comment is the primary delivery channel — the user reads results on the task board. - Do NOT start the next task until the current task is completed (default: one task in_progress at a time). For this reconnect turn: review the task board snapshot above and output a short summary (1–2 sentences) confirming reconnect is complete and you are ready.`; @@ -2390,8 +2395,8 @@ export class TeamProvisioningService { const label = status ? `API Error ${status}` : 'API Error'; const warningText = snippet - ? `**${label} — SDK is retrying**\n\n\`\`\`\n${snippet}\n\`\`\`\n\nОжидаем повторной попытки...` - : `**${label} — SDK is retrying**\n\nОжидаем повторной попытки...`; + ? `**${label} — SDK is retrying**\n\n\`\`\`\n${snippet}\n\`\`\`\n\nWaiting for retry...` + : `**${label} — SDK is retrying**\n\nWaiting for retry...`; run.provisioningOutputParts.push(warningText); run.progress.message = `${label} — SDK retrying...`; @@ -2434,11 +2439,11 @@ export class TeamProvisioningService { lastWarningAt = now; const silenceSec = Math.round(silenceMs / 1000); - run.provisioningOutputParts.push(this.buildStallWarningText(silenceSec)); + run.provisioningOutputParts.push(this.buildStallWarningText(silenceSec, run)); const mins = Math.floor(silenceSec / 60); const secs = silenceSec % 60; - const elapsed = mins > 0 ? `${mins} мин ${secs > 0 ? `${secs} сек` : ''}` : `${secs} сек`; - run.progress.message = `CLI не отвечает ${elapsed} — возможен rate limit`; + const elapsed = mins > 0 ? `${mins}m ${secs > 0 ? `${secs}s` : ''}` : `${secs}s`; + run.progress.message = `CLI not responding for ${elapsed} — possible rate limit`; emitLogsProgress(run); } catch (err) { logger.error( @@ -2457,40 +2462,45 @@ export class TeamProvisioningService { } } - private buildStallWarningText(silenceSec: number): string { + private buildStallWarningText(silenceSec: number, run: ProvisioningRun): string { const mins = Math.floor(silenceSec / 60); const secs = silenceSec % 60; - const elapsed = mins > 0 ? `${mins} мин ${secs > 0 ? `${secs} сек` : ''}` : `${secs} сек`; + const elapsed = mins > 0 ? `${mins}m ${secs > 0 ? `${secs}s` : ''}` : `${secs}s`; if (silenceSec < 60) { return ( `---\n\n` + - `**Ожидание ответа CLI** (тишина ${elapsed})\n\n` + - `Процесс запущен, но пока не выдаёт данных. ` + - `Это может быть вызвано задержкой API (rate limit / model cooldown) — ` + - `SDK выполняет повторные попытки автоматически.\n\n` + - `Ожидаем...` + `**Waiting for CLI response** (silent for ${elapsed})\n\n` + + `The process is running but not producing output yet. ` + + `This may be caused by an API delay (rate limit / model cooldown) — ` + + `the SDK retries automatically.\n\n` + + `Waiting...` ); } if (silenceSec < 120) { return ( `---\n\n` + - `**Ожидание ответа CLI** (тишина ${elapsed})\n\n` + - `Процесс по-прежнему не отвечает. Вероятна задержка из-за rate limiting ` + - `(ошибка 429 / model cooldown). SDK автоматически повторяет запрос — ` + - `обычно это проходит в течение 1-3 минут.\n\n` + - `Можно отменить и попробовать позже, если ожидание затянется.` + `**Waiting for CLI response** (silent for ${elapsed})\n\n` + + `The process is still not responding. Likely delayed due to rate limiting ` + + `(error 429 / model cooldown). The SDK retries the request automatically — ` + + `this usually resolves within 1-3 minutes.\n\n` + + `You can cancel and try again later if the wait continues.` ); } + const modelName = run.request.model ?? 'default'; + const effortLabel = run.request.effort ? ` (effort: ${run.request.effort})` : ''; + return ( `---\n\n` + - `**Длительное ожидание CLI** (тишина ${elapsed})\n\n` + - `Процесс молчит уже более ${mins} минут. Вероятные причины:\n` + - `- Rate limiting / model cooldown (429) — SDK повторяет автоматически\n` + - `- Перегрузка API сервера\n\n` + - `Рекомендуем отменить и попробовать через несколько минут.` + `**Extended CLI wait** (silent for ${elapsed})\n\n` + + `Model **${modelName}**${effortLabel} appears to be under heavy load and is not responding. ` + + `Most likely this is a 429 error (rate limit / model cooldown).\n\n` + + `The process has been silent for over ${mins} minutes. Possible causes:\n` + + `- Rate limiting / model cooldown (429) — SDK retries automatically\n` + + `- API server overload for this model\n\n` + + `Consider canceling and trying with a different model.` ); } @@ -5620,7 +5630,7 @@ export class TeamProvisioningService { `- BEFORE doing any work on a task: mark it started (in_progress).`, `- Immediately SendMessage "user" that you started task # (what you're doing + next step).`, `- While working: after each meaningful milestone/decision/blocker, add a task comment on #. If user-relevant, also SendMessage "user".`, - `- On completion: add a final task comment (what changed + how to verify), mark the task completed, then SendMessage "user" that task # is complete and what you will do next.`, + `- On completion: add a final task comment with your full results (findings, report, analysis, code changes summary, or any deliverable), then mark the task completed, then SendMessage "user" with a brief summary of the outcome (2-4 sentences) and mention that full details are in the task comment (include the commentId from the task_add_comment response). The task comment is the primary delivery channel — the user reads results on the task board.`, `- Do NOT start the next task until the current task is completed (default: one task in_progress at a time).`, board.trim(), ] diff --git a/src/renderer/components/dashboard/CliStatusBanner.tsx b/src/renderer/components/dashboard/CliStatusBanner.tsx index 60aae217..b59ecc40 100644 --- a/src/renderer/components/dashboard/CliStatusBanner.tsx +++ b/src/renderer/components/dashboard/CliStatusBanner.tsx @@ -109,6 +109,47 @@ const ErrorDisplay = ({ ); }; +// ============================================================================= +// CLI checking spinner with delayed hint +// ============================================================================= + +const SLOW_CHECK_DELAY_MS = 5_000; + +const CliCheckingSpinner = ({ + styles, +}: { + styles: { border: string; bg: string }; +}): React.JSX.Element => { + const [showHint, setShowHint] = useState(false); + + useEffect(() => { + const timer = setTimeout(() => setShowHint(true), SLOW_CHECK_DELAY_MS); + return () => clearTimeout(timer); + }, []); + + return ( +
+ +
+ + Checking Claude CLI... + + {showHint && ( +

+ First check may take up to 30 seconds +

+ )} +
+
+ ); +}; + // ============================================================================= // Installed banner (extracted sub-component) // ============================================================================= @@ -338,20 +379,7 @@ export const CliStatusBanner = (): React.JSX.Element | null => { } // Loading state: show spinner only while an actual request is in-flight. - return ( -
- - - Checking Claude CLI... - -
- ); + return ; } // ── Downloading ──────────────────────────────────────────────────────── diff --git a/src/renderer/components/dashboard/DashboardView.tsx b/src/renderer/components/dashboard/DashboardView.tsx index 2ac49c67..80b8c0a7 100644 --- a/src/renderer/components/dashboard/DashboardView.tsx +++ b/src/renderer/components/dashboard/DashboardView.tsx @@ -7,7 +7,7 @@ * - Border-first project cards with minimal backgrounds */ -import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'; +import React, { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react'; import { api } from '@renderer/api'; import { Button } from '@renderer/components/ui/button'; @@ -44,6 +44,7 @@ interface CommandSearchProps { const CommandSearch = ({ value, onChange }: Readonly): React.JSX.Element => { const [isFocused, setIsFocused] = useState(false); + const inputRef = useRef(null); const { openCommandPalette, selectedProjectId } = useStore( useShallow((s) => ({ openCommandPalette: s.openCommandPalette, @@ -64,6 +65,21 @@ const CommandSearch = ({ value, onChange }: Readonly): React return () => window.removeEventListener('keydown', handleKeyDown); }, [openCommandPalette]); + // Focus search when the dashboard mounts (packaged Electron can skip native autoFocus). + useLayoutEffect(() => { + const el = inputRef.current; + if (!el) { + return; + } + el.focus({ preventScroll: true }); + const t = window.setTimeout(() => { + if (document.activeElement !== el) { + el.focus({ preventScroll: true }); + } + }, 50); + return () => window.clearTimeout(t); + }, []); + return (
{/* Search container with glow effect on focus */} @@ -76,6 +92,7 @@ const CommandSearch = ({ value, onChange }: Readonly): React > onChange(e.target.value)} diff --git a/src/renderer/components/layout/Sidebar.tsx b/src/renderer/components/layout/Sidebar.tsx index 224f4b9b..29918c71 100644 --- a/src/renderer/components/layout/Sidebar.tsx +++ b/src/renderer/components/layout/Sidebar.tsx @@ -96,7 +96,7 @@ export const Sidebar = (): React.JSX.Element => { }} >
; + icon: LucideIcon; + description: string; electronOnly?: boolean; } const tabs: TabConfig[] = [ - { id: 'general', label: 'General', icon: Settings }, - // { id: 'connection', label: 'Connection', icon: Server, electronOnly: true }, - { id: 'notifications', label: 'Notifications', icon: Bell }, - { id: 'advanced', label: 'Advanced', icon: Wrench }, + { + id: 'general', + label: 'General', + icon: Settings, + description: + 'Core app preferences like theme, language, display density, and startup behavior.', + }, + // { id: 'connection', label: 'Connection', icon: Server, description: 'Manage CLI connection and authentication settings.', electronOnly: true }, + { + id: 'notifications', + label: 'Notifications', + icon: Bell, + description: + 'Control when and how you get notified about agent activity, task completions, and errors.', + }, + { + id: 'advanced', + label: 'Advanced', + icon: Wrench, + description: + 'Power-user options: export/import config, reset defaults, and raw configuration editing.', + }, ]; export const SettingsTabs = ({ activeSection, onSectionChange, }: Readonly): React.JSX.Element => { - const [hoveredTab, setHoveredTab] = useState(null); const isElectron = useMemo(() => isElectronMode(), []); const visibleTabs = useMemo( () => tabs.filter((tab) => !tab.electronOnly || isElectron), @@ -36,37 +62,53 @@ export const SettingsTabs = ({ ); return ( -
- {visibleTabs.map((tab) => { - const Icon = tab.icon; - const isActive = activeSection === tab.id; - const isHovered = hoveredTab === tab.id; + +
+
+ {visibleTabs.map((tab) => { + const Icon = tab.icon; + const isActive = activeSection === tab.id; - const getTextColor = (): string => { - if (isActive) return 'var(--color-text)'; - if (isHovered) return 'var(--color-text-secondary)'; - return 'var(--color-text-muted)'; - }; + return ( + - ); - })} -
+ + + event.stopPropagation()} + onMouseDown={(event) => event.stopPropagation()} + onKeyDown={(event) => { + if (event.key === 'Enter' || event.key === ' ') { + event.stopPropagation(); + } + }} + className="size-4.5 absolute right-1.5 top-0.5 z-10 inline-flex items-center justify-center rounded-full text-text-muted transition-colors hover:bg-[var(--color-surface-raised)] hover:text-text" + > + + + + + {tab.description} + + + + ); + })} +
+
+ ); }; diff --git a/src/renderer/components/settings/sections/AdvancedSection.tsx b/src/renderer/components/settings/sections/AdvancedSection.tsx index 9915f422..6ac12020 100644 --- a/src/renderer/components/settings/sections/AdvancedSection.tsx +++ b/src/renderer/components/settings/sections/AdvancedSection.tsx @@ -96,10 +96,10 @@ export const AdvancedSection = ({ return (
-
+
- {/* eslint-disable-next-line jsx-a11y/label-has-associated-control -- Checkbox is a custom component wrapping native input */} - +
+ + Comments + +
+ {READ_FILTER_OPTIONS.map((opt) => ( + + ))} +
+
+ {!collapsed && + entries.map(({ member, task, taskId, kind }) => { + const colors = getTeamColorSet(colorMap.get(member.name) ?? ''); + const roleLabel = formatAgentRole( + member.role ?? (member.agentType !== 'general-purpose' ? member.agentType : undefined) + ); + const dotPing = kind === 'reviewing' ? 'bg-amber-400' : 'bg-emerald-400'; + const dotSolid = kind === 'reviewing' ? 'bg-amber-500' : 'bg-emerald-500'; + const activityLabel = kind === 'reviewing' ? 'reviewing' : 'working on'; - return ( -
-
- - - - +
+ + - + + + + - - {onMemberClick ? ( - - ) : ( - - {displayMemberName(member.name)} - - )} - {roleLabel ? ( - - {roleLabel} - - ) : null} - - {activityLabel} - - {task && - (onTaskClick ? ( + {onMemberClick ? ( ) : ( - {formatTaskDisplayLabel(task)} {task.subject} + {displayMemberName(member.name)} - ))} -
-
- ); - })} + )} + {roleLabel ? ( + + {roleLabel} + + ) : null} + + {activityLabel} + + {task && + (onTaskClick ? ( + + ) : ( + + {formatTaskDisplayLabel(task)} {task.subject} + + ))} +
+ + ); + })}
); }; diff --git a/src/renderer/components/team/messages/MessagesPanel.tsx b/src/renderer/components/team/messages/MessagesPanel.tsx index 1bc7c59d..19d41c82 100644 --- a/src/renderer/components/team/messages/MessagesPanel.tsx +++ b/src/renderer/components/team/messages/MessagesPanel.tsx @@ -337,6 +337,7 @@ export const MessagesPanel = memo(function MessagesPanel({ tasks={tasks} messages={messages} pendingRepliesByMember={pendingRepliesByMember} + position="inline" onMemberClick={onMemberClick} onTaskClick={onTaskClick} /> @@ -514,9 +515,10 @@ export const MessagesPanel = memo(function MessagesPanel({ tasks={tasks} messages={messages} pendingRepliesByMember={pendingRepliesByMember} + position="sidebar" onMemberClick={onMemberClick} onTaskClick={onTaskClick} - /> + />{' '}
; + /** Where the Messages panel is rendered — 'sidebar' hides "In progress" (already visible in MemberList). */ + position?: 'sidebar' | 'inline'; onMemberClick?: (member: ResolvedTeamMember) => void; onTaskClick?: (task: TeamTaskWithKanban) => void; } @@ -28,6 +30,7 @@ export const StatusBlock = ({ tasks, messages, pendingRepliesByMember, + position, onMemberClick, onTaskClick, }: StatusBlockProps): React.JSX.Element | null => { @@ -92,6 +95,7 @@ export const StatusBlock = ({ diff --git a/src/renderer/components/terminal/TerminalLogPanel.tsx b/src/renderer/components/terminal/TerminalLogPanel.tsx index 7d769d81..defc34d4 100644 --- a/src/renderer/components/terminal/TerminalLogPanel.tsx +++ b/src/renderer/components/terminal/TerminalLogPanel.tsx @@ -73,7 +73,8 @@ export const TerminalLogPanel = ({ if (!term) return; for (let i = writtenRef.current; i < chunks.length; i++) { - term.write(chunks[i]); + // xterm requires \r\n for proper line breaks; normalize bare \n from process output + term.write(chunks[i].replace(/\r?\n/g, '\r\n')); } writtenRef.current = chunks.length; }, [chunks]); diff --git a/src/renderer/components/ui/tiptap/useTiptapEditor.ts b/src/renderer/components/ui/tiptap/useTiptapEditor.ts index 32ea7888..c87ee52a 100644 --- a/src/renderer/components/ui/tiptap/useTiptapEditor.ts +++ b/src/renderer/components/ui/tiptap/useTiptapEditor.ts @@ -48,10 +48,6 @@ export function useTiptapEditor({ editable, shouldRerenderOnTransaction: false, // v3 performance — toolbar использует useEditorState autofocus: autoFocus ? 'end' : false, - enableContentCheck: true, - onContentError: ({ error }) => { - console.error('[TiptapEditor] Content error:', error); - }, onUpdate: ({ editor: e }) => { if (isProgrammaticUpdate.current) return; try { diff --git a/test/main/services/team/TeamProvisioningServicePrompts.test.ts b/test/main/services/team/TeamProvisioningServicePrompts.test.ts index 1ace727c..d66df320 100644 --- a/test/main/services/team/TeamProvisioningServicePrompts.test.ts +++ b/test/main/services/team/TeamProvisioningServicePrompts.test.ts @@ -246,7 +246,7 @@ describe('TeamProvisioningService prompt content (solo mode discipline)', () => 'leave a short task comment on that waiting task right away with the reason and your best ETA, keep it in pending/TODO' ); expect(prompt).toContain( - 'Direct messages to your team lead are only for urgent attention, no-task situations, or when the lead explicitly asked for a direct reply.' + 'Beyond task-completion pings, direct messages to your team lead are only for urgent attention, no-task situations, or when the lead explicitly asked for a direct reply.' ); expect(prompt).toContain( 'do NOT send a duplicate SendMessage to the lead with the same content unless you need urgent non-task attention.'