diff --git a/tasksync-chat/media/webview.js b/tasksync-chat/media/webview.js
index b6e3109..99c3515 100644
--- a/tasksync-chat/media/webview.js
+++ b/tasksync-chat/media/webview.js
@@ -29,7 +29,11 @@
let autopilotText = '';
let autopilotTextDebounceTimer = null;
let responseTimeout = 60;
+ let sessionWarningHours = 2;
let maxConsecutiveAutoResponses = 5;
+ // Keep timeout options aligned with select values to avoid invalid UI state.
+ var RESPONSE_TIMEOUT_ALLOWED_VALUES = new Set([0, 5, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120, 150, 180, 210, 240]);
+ var RESPONSE_TIMEOUT_DEFAULT = 60;
// Human-like delay: random jitter simulates natural reading/typing time
let humanLikeDelayEnabled = true;
let humanLikeDelayMin = 2; // minimum seconds
@@ -80,7 +84,7 @@
// Settings modal elements
let settingsModal, settingsModalOverlay, settingsModalClose;
let soundToggle, interactiveApprovalToggle, sendShortcutToggle, autopilotEditBtn, autopilotToggle, autopilotTextInput, promptsList, addPromptBtn, addPromptForm;
- let responseTimeoutSelect, maxAutoResponsesInput;
+ let responseTimeoutSelect, sessionWarningHoursSelect, maxAutoResponsesInput;
let humanDelayToggle, humanDelayRangeContainer, humanDelayMinInput, humanDelayMaxInput;
function init() {
@@ -431,6 +435,31 @@
'';
modalContent.appendChild(timeoutSection);
+ // Session Warning section - warning threshold in hours
+ var sessionWarningSection = document.createElement('div');
+ sessionWarningSection.className = 'settings-section';
+ sessionWarningSection.innerHTML = '
' +
+ '' +
+ '' +
+ '
';
+ modalContent.appendChild(sessionWarningSection);
+
// Max Consecutive Auto-Responses section - number input
var maxAutoSection = document.createElement('div');
maxAutoSection.className = 'settings-section';
@@ -498,6 +527,7 @@
autopilotEditBtn = document.getElementById('autopilot-edit-btn');
autopilotTextInput = document.getElementById('autopilot-text');
responseTimeoutSelect = document.getElementById('response-timeout-select');
+ sessionWarningHoursSelect = document.getElementById('session-warning-hours-select');
maxAutoResponsesInput = document.getElementById('max-auto-responses-input');
humanDelayToggle = document.getElementById('human-delay-toggle');
humanDelayRangeContainer = document.getElementById('human-delay-range');
@@ -620,6 +650,9 @@
if (responseTimeoutSelect) {
responseTimeoutSelect.addEventListener('change', handleResponseTimeoutChange);
}
+ if (sessionWarningHoursSelect) {
+ sessionWarningHoursSelect.addEventListener('change', handleSessionWarningHoursChange);
+ }
if (maxAutoResponsesInput) {
maxAutoResponsesInput.addEventListener('change', handleMaxAutoResponsesChange);
maxAutoResponsesInput.addEventListener('blur', handleMaxAutoResponsesChange);
@@ -1001,6 +1034,16 @@
if (queueSection) queueSection.classList.toggle('collapsed');
}
+ function normalizeResponseTimeout(value) {
+ if (!Number.isFinite(value)) {
+ return RESPONSE_TIMEOUT_DEFAULT;
+ }
+ if (!RESPONSE_TIMEOUT_ALLOWED_VALUES.has(value)) {
+ return RESPONSE_TIMEOUT_DEFAULT;
+ }
+ return value;
+ }
+
function handleExtensionMessage(event) {
var message = event.data;
console.log('[TaskSync Webview] Received message:', message.type, message);
@@ -1047,7 +1090,8 @@
autopilotEnabled = message.autopilotEnabled === true;
autopilotText = typeof message.autopilotText === 'string' ? message.autopilotText : '';
reusablePrompts = message.reusablePrompts || [];
- responseTimeout = typeof message.responseTimeout === 'number' ? message.responseTimeout : 60;
+ responseTimeout = normalizeResponseTimeout(message.responseTimeout);
+ sessionWarningHours = typeof message.sessionWarningHours === 'number' ? message.sessionWarningHours : 2;
maxConsecutiveAutoResponses = typeof message.maxConsecutiveAutoResponses === 'number' ? message.maxConsecutiveAutoResponses : 5;
humanLikeDelayEnabled = message.humanLikeDelayEnabled !== false;
humanLikeDelayMin = typeof message.humanLikeDelayMin === 'number' ? message.humanLikeDelayMin : 2;
@@ -1058,6 +1102,7 @@
updateAutopilotToggleUI();
updateAutopilotTextUI();
updateResponseTimeoutUI();
+ updateSessionWarningHoursUI();
updateMaxAutoResponsesUI();
updateHumanDelayUI();
renderPromptsList();
@@ -2185,6 +2230,23 @@
responseTimeoutSelect.value = String(responseTimeout);
}
+ function handleSessionWarningHoursChange() {
+ if (!sessionWarningHoursSelect) return;
+
+ var value = parseInt(sessionWarningHoursSelect.value, 10);
+ if (!isNaN(value) && value >= 0 && value <= 8) {
+ sessionWarningHours = value;
+ vscode.postMessage({ type: 'updateSessionWarningHours', value: value });
+ }
+
+ sessionWarningHoursSelect.value = String(sessionWarningHours);
+ }
+
+ function updateSessionWarningHoursUI() {
+ if (!sessionWarningHoursSelect) return;
+ sessionWarningHoursSelect.value = String(sessionWarningHours);
+ }
+
function handleMaxAutoResponsesChange() {
if (!maxAutoResponsesInput) return;
var value = parseInt(maxAutoResponsesInput.value, 10);
diff --git a/tasksync-chat/package.json b/tasksync-chat/package.json
index 8f6be84..0961228 100644
--- a/tasksync-chat/package.json
+++ b/tasksync-chat/package.json
@@ -2,7 +2,7 @@
"name": "tasksync-chat",
"publisher": "4regab",
"displayName": "TaskSync",
- "description": "Automate AI conversations. Queue your prompts or tasks. Work uninterrupted.",
+ "description": "Queue your prompts or tasks. Work uninterrupted.",
"icon": "media/Tasksync-logo.png",
"version": "2.0.21",
"engines": {
@@ -160,6 +160,14 @@
],
"description": "Auto-respond to pending tool calls if user doesn't respond within this time. When Autopilot is enabled, the Autopilot text is sent. When disabled, a session termination message is sent."
},
+ "tasksync.sessionWarningHours": {
+ "type": "number",
+ "default": 2,
+ "minimum": 0,
+ "maximum": 8,
+ "scope": "window",
+ "description": "Show a one-time warning after this many hours (0..8) in the same session. Set to 0 to disable session warning."
+ },
"tasksync.maxConsecutiveAutoResponses": {
"type": "number",
"default": 5,
@@ -302,4 +310,4 @@
"esbuild": "^0.27.2",
"typescript": "^5.3.3"
}
-}
+}
\ No newline at end of file
diff --git a/tasksync-chat/src/webview/webviewProvider.ts b/tasksync-chat/src/webview/webviewProvider.ts
index d9eab42..91b5638 100644
--- a/tasksync-chat/src/webview/webviewProvider.ts
+++ b/tasksync-chat/src/webview/webviewProvider.ts
@@ -75,7 +75,7 @@ type ToWebviewMessage =
| { type: 'updateAttachments'; attachments: AttachmentInfo[] }
| { type: 'imageSaved'; attachment: AttachmentInfo }
| { type: 'openSettingsModal' }
- | { type: 'updateSettings'; soundEnabled: boolean; interactiveApprovalEnabled: boolean; autopilotEnabled: boolean; autopilotText: string; reusablePrompts: ReusablePrompt[]; responseTimeout: number; maxConsecutiveAutoResponses: number; humanLikeDelayEnabled: boolean; humanLikeDelayMin: number; humanLikeDelayMax: number; sendWithCtrlEnter: boolean }
+ | { type: 'updateSettings'; soundEnabled: boolean; interactiveApprovalEnabled: boolean; autopilotEnabled: boolean; autopilotText: string; reusablePrompts: ReusablePrompt[]; responseTimeout: number; sessionWarningHours: number; maxConsecutiveAutoResponses: number; humanLikeDelayEnabled: boolean; humanLikeDelayMin: number; humanLikeDelayMax: number; sendWithCtrlEnter: boolean }
| { type: 'slashCommandResults'; prompts: ReusablePrompt[] }
| { type: 'playNotificationSound' }
| { type: 'contextSearchResults'; suggestions: Array<{ type: string; label: string; description: string; detail: string }> }
@@ -114,6 +114,7 @@ type FromWebviewMessage =
| { type: 'openExternal'; url: string }
| { type: 'openFileLink'; target: string }
| { type: 'updateResponseTimeout'; value: number }
+ | { type: 'updateSessionWarningHours'; value: number }
| { type: 'updateMaxConsecutiveAutoResponses'; value: number }
| { type: 'updateHumanDelaySetting'; enabled: boolean }
| { type: 'updateHumanDelayMin'; value: number }
@@ -197,6 +198,15 @@ export class TaskSyncWebviewProvider implements vscode.WebviewViewProvider, vsco
private _humanLikeDelayMin: number = 2; // seconds
private _humanLikeDelayMax: number = 6; // seconds
+ // Session warning threshold (hours). 0 disables the warning.
+ private _sessionWarningHours: number = 2;
+
+ // Allowed timeout values (minutes) shared across config reads/writes and UI sync.
+ private readonly _RESPONSE_TIMEOUT_ALLOWED_MINUTES = new Set([
+ 0, 5, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120, 150, 180, 210, 240
+ ]);
+ private readonly _RESPONSE_TIMEOUT_DEFAULT_MINUTES = 60;
+
// Send behavior: false => Enter, true => Ctrl/Cmd+Enter
private _sendWithCtrlEnter: boolean = false;
@@ -253,6 +263,7 @@ export class TaskSyncWebviewProvider implements vscode.WebviewViewProvider, vsco
e.affectsConfiguration('tasksync.autoAnswerText') ||
e.affectsConfiguration('tasksync.reusablePrompts') ||
e.affectsConfiguration('tasksync.responseTimeout') ||
+ e.affectsConfiguration('tasksync.sessionWarningHours') ||
e.affectsConfiguration('tasksync.maxConsecutiveAutoResponses') ||
e.affectsConfiguration('tasksync.humanLikeDelay') ||
e.affectsConfiguration('tasksync.humanLikeDelayMin') ||
@@ -469,12 +480,14 @@ export class TaskSyncWebviewProvider implements vscode.WebviewViewProvider, vsco
if (this._view) {
this._view.title = this._formatElapsed(elapsed);
}
- // Show a one-time warning after 2 hours of session activity
- if (!this._sessionWarningShown && elapsed >= 2 * 60 * 60 * 1000) {
+ // Warn once when a long-running session crosses the configured threshold.
+ const warningThresholdMs = this._sessionWarningHours * 60 * 60 * 1000;
+ if (this._sessionWarningHours > 0 && !this._sessionWarningShown && elapsed >= warningThresholdMs) {
this._sessionWarningShown = true;
const callCount = this._currentSessionCalls.length;
+ const hoursLabel = this._sessionWarningHours === 1 ? 'hour' : 'hours';
vscode.window.showWarningMessage(
- `Your session has been running for over 2 hours (${callCount} tool calls). Consider starting a new session to maintain quality.`,
+ `Your session has been running for over ${this._sessionWarningHours} ${hoursLabel} (${callCount} tool calls). Consider starting a new session to maintain quality.`,
'New Session',
'Dismiss'
).then(action => {
@@ -531,6 +544,36 @@ export class TaskSyncWebviewProvider implements vscode.WebviewViewProvider, vsco
return text.trim().length > 0 ? text : defaultAutopilotText;
}
+ private _normalizeResponseTimeout(value: unknown): number {
+ let parsedValue: number;
+
+ if (typeof value === 'number') {
+ parsedValue = value;
+ } else if (typeof value === 'string') {
+ const normalizedValue = value.trim();
+ if (normalizedValue.length === 0) {
+ return this._RESPONSE_TIMEOUT_DEFAULT_MINUTES;
+ }
+ parsedValue = Number(normalizedValue);
+ } else {
+ return this._RESPONSE_TIMEOUT_DEFAULT_MINUTES;
+ }
+
+ if (!Number.isFinite(parsedValue) || !Number.isInteger(parsedValue)) {
+ return this._RESPONSE_TIMEOUT_DEFAULT_MINUTES;
+ }
+ if (!this._RESPONSE_TIMEOUT_ALLOWED_MINUTES.has(parsedValue)) {
+ return this._RESPONSE_TIMEOUT_DEFAULT_MINUTES;
+ }
+ return parsedValue;
+ }
+
+ private _readResponseTimeoutMinutes(config?: vscode.WorkspaceConfiguration): number {
+ const settings = config ?? vscode.workspace.getConfiguration('tasksync');
+ const configuredTimeout = settings.get('responseTimeout', String(this._RESPONSE_TIMEOUT_DEFAULT_MINUTES));
+ return this._normalizeResponseTimeout(configuredTimeout);
+ }
+
private _loadSettings(): void {
const config = vscode.workspace.getConfiguration('tasksync');
this._soundEnabled = config.get('notificationSound', true);
@@ -592,6 +635,10 @@ export class TaskSyncWebviewProvider implements vscode.WebviewViewProvider, vsco
this._humanLikeDelayEnabled = config.get('humanLikeDelay', true);
this._humanLikeDelayMin = config.get('humanLikeDelayMin', 2);
this._humanLikeDelayMax = config.get('humanLikeDelayMax', 6);
+ const configuredWarningHours = config.get('sessionWarningHours', 2);
+ this._sessionWarningHours = Number.isFinite(configuredWarningHours)
+ ? Math.min(8, Math.max(0, Math.floor(configuredWarningHours)))
+ : 2;
this._sendWithCtrlEnter = config.get('sendWithCtrlEnter', false);
// Ensure min <= max
if (this._humanLikeDelayMin > this._humanLikeDelayMax) {
@@ -621,7 +668,7 @@ export class TaskSyncWebviewProvider implements vscode.WebviewViewProvider, vsco
*/
private _updateSettingsUI(): void {
const config = vscode.workspace.getConfiguration('tasksync');
- const responseTimeout = parseInt(config.get('responseTimeout', '60'), 10);
+ const responseTimeout = this._readResponseTimeoutMinutes(config);
const maxConsecutiveAutoResponses = config.get('maxConsecutiveAutoResponses', 5);
this._view?.webview.postMessage({
@@ -632,6 +679,7 @@ export class TaskSyncWebviewProvider implements vscode.WebviewViewProvider, vsco
autopilotText: this._autopilotText,
reusablePrompts: this._reusablePrompts,
responseTimeout: responseTimeout,
+ sessionWarningHours: this._sessionWarningHours,
maxConsecutiveAutoResponses: maxConsecutiveAutoResponses,
humanLikeDelayEnabled: this._humanLikeDelayEnabled,
humanLikeDelayMin: this._humanLikeDelayMin,
@@ -980,12 +1028,10 @@ export class TaskSyncWebviewProvider implements vscode.WebviewViewProvider, vsco
}
// Get timeout from config (in minutes)
- const config = vscode.workspace.getConfiguration('tasksync');
- const rawValue = config.get('responseTimeout', '60');
- const timeoutMinutes = parseInt(rawValue, 10);
+ const timeoutMinutes = this._readResponseTimeoutMinutes();
// If timeout is 0 or disabled, don't start a timer
- if (timeoutMinutes <= 0 || isNaN(timeoutMinutes)) {
+ if (timeoutMinutes <= 0) {
return;
}
@@ -1020,7 +1066,7 @@ export class TaskSyncWebviewProvider implements vscode.WebviewViewProvider, vsco
// Use autopilot text only if autopilot is enabled; otherwise use session termination message
const config = vscode.workspace.getConfiguration('tasksync');
- const timeoutMinutes = config.get('responseTimeout', '60');
+ const timeoutMinutes = this._readResponseTimeoutMinutes(config);
const maxConsecutive = config.get('maxConsecutiveAutoResponses', 5);
// Increment and enforce consecutive auto-response limit
@@ -1178,6 +1224,9 @@ export class TaskSyncWebviewProvider implements vscode.WebviewViewProvider, vsco
case 'updateResponseTimeout':
this._handleUpdateResponseTimeout(message.value);
break;
+ case 'updateSessionWarningHours':
+ this._handleUpdateSessionWarningHours(message.value);
+ break;
case 'updateMaxConsecutiveAutoResponses':
this._handleUpdateMaxConsecutiveAutoResponses(message.value);
break;
@@ -1933,7 +1982,28 @@ export class TaskSyncWebviewProvider implements vscode.WebviewViewProvider, vsco
this._isUpdatingConfig = true;
try {
const config = vscode.workspace.getConfiguration('tasksync');
- await config.update('responseTimeout', String(value), vscode.ConfigurationTarget.Workspace);
+ const normalizedValue = this._normalizeResponseTimeout(value);
+ await config.update('responseTimeout', String(normalizedValue), vscode.ConfigurationTarget.Workspace);
+ } finally {
+ this._isUpdatingConfig = false;
+ }
+ }
+
+ /**
+ * Handle updating session warning threshold in hours.
+ */
+ private async _handleUpdateSessionWarningHours(value: number): Promise {
+ if (!Number.isFinite(value)) {
+ return;
+ }
+
+ const normalizedValue = Math.min(8, Math.max(0, Math.floor(value)));
+ this._sessionWarningHours = normalizedValue;
+
+ this._isUpdatingConfig = true;
+ try {
+ const config = vscode.workspace.getConfiguration('tasksync');
+ await config.update('sessionWarningHours', normalizedValue, vscode.ConfigurationTarget.Workspace);
} finally {
this._isUpdatingConfig = false;
}
@@ -2024,7 +2094,7 @@ export class TaskSyncWebviewProvider implements vscode.WebviewViewProvider, vsco
this._isUpdatingConfig = true;
try {
const config = vscode.workspace.getConfiguration('tasksync');
- await config.update('sendWithCtrlEnter', enabled, vscode.ConfigurationTarget.Workspace);
+ await config.update('sendWithCtrlEnter', enabled, vscode.ConfigurationTarget.Global);
} finally {
this._isUpdatingConfig = false;
}