diff --git a/CHANGELOG.md b/CHANGELOG.md index 00e615c..e5e1008 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ All notable changes to this project will be documented in this file. +## TaskSync v3.0.10 (04-03-26) +- feat: add agent orchestration toggle, single-session routing mode, and always-returned session_id tool payloads +- fix: tighten the gap below the view toolbar, focus dialogs on open, and let TaskSync dialogs close on `Escape` + ## TaskSync v3.0.7 (03-28-26) - fix: remove deprecated wordWrap (redundant with overflowWrap) diff --git a/README.md b/README.md index 9f416ad..f110dc9 100644 --- a/README.md +++ b/README.md @@ -25,6 +25,7 @@ A dedicated VS Code sidebar extension with smart prompt queue system. _Setup ins **Features:** - Smart Queue Mode - batch responses for AI agents - Autopilot - let agents work autonomously with customizable auto-responses +- Agent orchestration toggle - switch between multi-session routing and a single-session lane per workspace - Remote Access - control from your phone via LAN or Tailscale, with PWA and code review - Give new tasks/feedback using ask_user tool - File, folder, tool, and context references with `#` autocomplete diff --git a/tasksync-chat/README.md b/tasksync-chat/README.md index 945a222..1adee15 100644 --- a/tasksync-chat/README.md +++ b/tasksync-chat/README.md @@ -69,6 +69,12 @@ Paste or drag-and-drop images directly into the chat input. Images are automatic - Access full history via the history button in the title bar - Remove individual entries or clear all history +### Agent Orchestration +Choose how TaskSync manages sessions: +- **Enabled by default**: keep separate agent sessions, the sessions list, session switching, and split view +- **Disabled**: stay in one single-session lane; TaskSync hides the sessions list, turns off split view, and routes every `ask_user` call into the active TaskSync session +- Turning it off hides extra sessions. It does not delete them. Turn it back on to see them again. + ### Auto Append Two features for appending text to every `ask_user` response: @@ -224,7 +230,10 @@ In VS Code Settings (search "tasksync"): **Debug:** - `tasksync.debugLogging`: Verbose extension debug logging (default: false) -All other settings (Autopilot, timeout, human-like delay, sound, etc.) are managed through the TaskSync Settings modal (gear icon). +**Session Model:** +- `tasksync.agentOrchestration`: Keep separate TaskSync agent sessions, the sessions list, switching, and split view. Turn it off to force single-session routing in the current workspace. (default: true) + +Most other settings (Autopilot, timeout, human-like delay, sound, etc.) are managed through the TaskSync Settings modal (gear icon). ## Requirements diff --git a/tasksync-chat/media/main.css b/tasksync-chat/media/main.css index 8846fac..78650c1 100644 --- a/tasksync-chat/media/main.css +++ b/tasksync-chat/media/main.css @@ -297,7 +297,7 @@ body { align-items: center; justify-content: space-between; gap: 8px; - padding: 10px 12px; + padding: 6px 12px; border-bottom: 1px solid var(--vscode-panel-border); } @@ -3206,6 +3206,20 @@ button#send-btn:disabled { box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3); } +.settings-modal-overlay:focus, +.settings-modal-overlay:focus-visible, +.history-modal-overlay:focus, +.history-modal-overlay:focus-visible { + outline: none; +} + +.settings-modal:focus, +.settings-modal:focus-visible, +.history-modal:focus, +.history-modal:focus-visible { + outline: none; +} + .settings-modal-header { display: flex; align-items: center; @@ -3710,10 +3724,14 @@ button#send-btn:disabled { appearance: none; -webkit-appearance: none; -moz-appearance: none; - background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath fill='%23888' d='M4.5 5.5L8 9l3.5-3.5z'/%3E%3C/svg%3E"); + background-image: + linear-gradient(45deg, transparent 50%, #888 50%), + linear-gradient(135deg, #888 50%, transparent 50%); background-repeat: no-repeat; - background-position: right 8px center; - background-size: 12px; + background-position: + calc(100% - 13px) calc(50% - 2px), + calc(100% - 8px) calc(50% - 2px); + background-size: 6px 6px, 6px 6px; padding-right: 28px; cursor: pointer; width: 100%; diff --git a/tasksync-chat/media/webview.js b/tasksync-chat/media/webview.js index 1dcf50c..942c141 100644 --- a/tasksync-chat/media/webview.js +++ b/tasksync-chat/media/webview.js @@ -293,6 +293,16 @@ function mapToRemoteMessage(msg) { return null; // Multi-session operations — forward to server as-is case "switchSession": + if (!agentOrchestrationEnabled) { + if (typeof syncClientSessionSelection === "function") { + syncClientSessionSelection( + serverActiveSessionId || activeSessionId || null, + ); + } + renderSessionsList(); + updateWelcomeSectionVisibility(); + return null; + } if (!msg.sessionId) { // Back to hub — handle locally, no server round-trip needed if (typeof saveActiveSessionComposerState === "function") { @@ -741,6 +751,17 @@ function applySettingsData(s) { queueEnabled = s.queueEnabled; updateQueueVisibility(); } + if (s.agentOrchestrationEnabled !== undefined) { + agentOrchestrationEnabled = s.agentOrchestrationEnabled; + if (!agentOrchestrationEnabled) { + splitViewEnabled = false; + if (typeof syncClientSessionSelection === "function") { + syncClientSessionSelection( + serverActiveSessionId || activeSessionId || null, + ); + } + } + } if (s.autoAppendEnabled !== undefined) { autoAppendEnabled = s.autoAppendEnabled; } @@ -889,6 +910,7 @@ function updatePendingUI() { function applySettingsToUI() { updateSoundToggleUI(); updateInteractiveApprovalToggleUI(); + updateAgentOrchestrationToggleUI(); updateAutoAppendToggleUI(); updateAutoAppendTextUI(); updateSendWithCtrlEnterToggleUI(); @@ -901,6 +923,8 @@ function applySettingsToUI() { workspacePromptListUI.render(); renderPromptsList(); updateQueueVisibility(); + renderSessionsList(); + updateWelcomeSectionVisibility(); } // ==================== End Communication Adapter ==================== @@ -1015,6 +1039,7 @@ let lastPendingContentHtml = ""; // Settings state (initialized from constants to maintain SSOT) let soundEnabled = true; let interactiveApprovalEnabled = true; +let agentOrchestrationEnabled = true; let autoAppendEnabled = false; let autoAppendText = ""; // Custom text appended to responses for the active session let alwaysAppendReminder = false; // Global AskUser reminder toggle @@ -1116,6 +1141,7 @@ let simpleAlertModalOverlay = null; let settingsModal, settingsModalOverlay, settingsModalClose; let soundToggle, interactiveApprovalToggle, + agentOrchestrationToggle, autoAppendToggle, autoAppendTextRow, autoAppendTextInput, @@ -1161,6 +1187,45 @@ function sessionExists(sessionId) { ); } +function resolveSingleSessionId() { + if (sessionExists(serverActiveSessionId)) { + return serverActiveSessionId; + } + if (sessionExists(activeSessionId)) { + return activeSessionId; + } + var fallbackSession = Array.isArray(sessions) + ? sessions.find(function (session) { + return session.status === "active"; + }) + : null; + return fallbackSession ? fallbackSession.id : null; +} + +function getVisibleSessions() { + if (agentOrchestrationEnabled) { + return Array.isArray(sessions) ? sessions : []; + } + var singletonSessionId = resolveSingleSessionId(); + if (!singletonSessionId) { + return []; + } + return sessions.filter(function (session) { + return session.id === singletonSessionId; + }); +} + +function getWaitingActiveSessions() { + return Array.isArray(sessions) + ? sessions.filter(function (session) { + return ( + session.status === "active" && + (session.waitingOnUser || !!session.pendingToolCallId) + ); + }) + : []; +} + function requestFollowServerActiveSession() { followServerActiveSessionOnce = true; } @@ -1168,6 +1233,12 @@ function requestFollowServerActiveSession() { function syncClientSessionSelection(nextServerActiveSessionId) { serverActiveSessionId = nextServerActiveSessionId || null; + if (!agentOrchestrationEnabled) { + activeSessionId = resolveSingleSessionId(); + followServerActiveSessionOnce = false; + return; + } + if (!sessionExists(activeSessionId)) { activeSessionId = null; } @@ -1196,7 +1267,11 @@ function getSubmitSessionId() { } function isSplitViewLayoutActive() { - return splitViewEnabled && sessionExists(activeSessionId); + return ( + agentOrchestrationEnabled && + splitViewEnabled && + sessionExists(activeSessionId) + ); } function init() { try { @@ -1210,6 +1285,7 @@ function init() { initSessionPromptListUI(); createNewSessionModal(); createResetSessionModal(); + createDisableAgentOrchestrationModal(); createTimeoutWarningModal(); createSimpleAlertModal(); bindEventListeners(); @@ -1411,6 +1487,7 @@ function createHistoryModal() { historyModal.setAttribute("role", "dialog"); historyModal.setAttribute("aria-modal", "true"); historyModal.setAttribute("aria-label", "Session History"); + historyModal.tabIndex = -1; // Modal header let modalHeader = document.createElement("div"); @@ -1555,6 +1632,7 @@ function createSettingsModal() { settingsModal.id = "settings-modal"; settingsModal.setAttribute("role", "dialog"); settingsModal.setAttribute("aria-labelledby", "settings-modal-title"); + settingsModal.tabIndex = -1; // Modal header let modalHeader = document.createElement("div"); @@ -1618,6 +1696,20 @@ function createSettingsModal() { ""; modalContent.appendChild(approvalSection); + // Agent orchestration section - toggle between multi-session and single-session mode + let agentOrchestrationSection = document.createElement("div"); + agentOrchestrationSection.className = "settings-section"; + agentOrchestrationSection.innerHTML = + '
,
+): ChatSession[] {
+ return p._sessionManager
+ .getActiveSessions()
+ .filter((session) => session.waitingOnUser || !!session.pendingToolCallId);
+}
+
+const STOP_AND_DISABLE_REASON =
+ "[Session stopped before disabling Agent Orchestration]";
+
export function updateSettingsUI(p: P): void {
const payload = buildSettingsPayload(p);
p._view?.webview.postMessage({
@@ -361,6 +404,51 @@ export async function handleUpdateInteractiveApprovalSetting(
});
}
+export async function handleUpdateAgentOrchestrationSetting(
+ p: P,
+ enabled: boolean,
+): Promise