@@ -104,6 +104,7 @@ import { createSchedulerWebviewTransientState } from "./cockpitWebviewTransientS
104104 var strings = bootstrapData . strings ;
105105 var currentLogLevel = bootstrapData . currentLogLevel ;
106106 var currentLogDirectory = bootstrapData . currentLogDirectory ;
107+ var storageStatusRefreshNoteTimer = null ;
107108
108109 function refreshTaskCountdowns ( ) {
109110 if ( ! taskList || ! taskList . isConnected ) {
@@ -388,8 +389,14 @@ import { createSchedulerWebviewTransientState } from "./cockpitWebviewTransientS
388389 restoreHistoryBtn,
389390 autoShowStartupNote,
390391 friendlyBuilder,
392+ recurringScheduleGroup,
393+ oneTimeDelayGroup,
391394 cronPreset,
392395 cronExpression,
396+ oneTimeDelayHours,
397+ oneTimeDelayMinutes,
398+ oneTimeDelaySeconds,
399+ oneTimeDelayPreviewText,
393400 agentSelect,
394401 modelSelect,
395402 chatSessionGroup,
@@ -408,6 +415,8 @@ import { createSchedulerWebviewTransientState } from "./cockpitWebviewTransientS
408415 syncBundledAgentsBtn,
409416 openCopilotSettingsBtn,
410417 openExtensionSettingsBtn,
418+ refreshStorageStatusBtn,
419+ settingsStatusRefreshNote,
411420 importStorageFromJsonBtn,
412421 exportStorageToJsonBtn,
413422 helpLanguageSelect,
@@ -429,6 +438,7 @@ import { createSchedulerWebviewTransientState } from "./cockpitWebviewTransientS
429438 taskFilterBar,
430439 taskLabelFilter,
431440 taskLabelsInput,
441+ runFirstGroup,
432442 jobsFolderList,
433443 jobsCurrentFolderBanner,
434444 jobsList,
@@ -1409,6 +1419,22 @@ import { createSchedulerWebviewTransientState } from "./cockpitWebviewTransientS
14091419 }
14101420 }
14111421
1422+ function showStorageStatusRefreshNote ( ) {
1423+ if ( ! settingsStatusRefreshNote ) {
1424+ return ;
1425+ }
1426+ settingsStatusRefreshNote . textContent = strings . settingsStatusUpdated || "✓ Updated" ;
1427+ settingsStatusRefreshNote . style . opacity = "1" ;
1428+ if ( storageStatusRefreshNoteTimer ) {
1429+ window . clearTimeout ( storageStatusRefreshNoteTimer ) ;
1430+ }
1431+ storageStatusRefreshNoteTimer = window . setTimeout ( function ( ) {
1432+ settingsStatusRefreshNote . style . opacity = "0" ;
1433+ settingsStatusRefreshNote . textContent = "" ;
1434+ storageStatusRefreshNoteTimer = null ;
1435+ } , 2000 ) ;
1436+ }
1437+
14121438 function renderLoggingControls ( ) {
14131439 if ( settingsLogLevelSelect ) {
14141440 settingsLogLevelSelect . value = currentLogLevel || "info" ;
@@ -1550,9 +1576,30 @@ import { createSchedulerWebviewTransientState } from "./cockpitWebviewTransientS
15501576 function syncRecurringChatSessionUi ( ) {
15511577 var oneTimeEl = document . getElementById ( "one-time" ) ;
15521578 var manualSessionEl = document . getElementById ( "manual-session" ) ;
1579+ var runFirstEl = document . getElementById ( "run-first" ) ;
15531580 var isOneTime = ! ! ( oneTimeEl && oneTimeEl . checked ) ;
15541581 var isManualSession = ! ! ( manualSessionEl && manualSessionEl . checked ) ;
15551582
1583+ if ( isOneTime && manualSessionEl && manualSessionEl . checked ) {
1584+ manualSessionEl . checked = false ;
1585+ isManualSession = false ;
1586+ }
1587+
1588+ if ( isManualSession && oneTimeEl && oneTimeEl . checked ) {
1589+ oneTimeEl . checked = false ;
1590+ isOneTime = false ;
1591+ }
1592+
1593+ if ( recurringScheduleGroup ) {
1594+ recurringScheduleGroup . style . display = isOneTime ? "none" : "" ;
1595+ }
1596+ if ( oneTimeDelayGroup ) {
1597+ oneTimeDelayGroup . style . display = isOneTime ? "block" : "none" ;
1598+ }
1599+ if ( runFirstGroup ) {
1600+ runFirstGroup . style . display = isOneTime ? "none" : "block" ;
1601+ }
1602+
15561603 if ( chatSessionGroup ) {
15571604 chatSessionGroup . style . display = isOneTime ? "none" : "block" ;
15581605 }
@@ -1565,13 +1612,94 @@ import { createSchedulerWebviewTransientState } from "./cockpitWebviewTransientS
15651612 chatSessionSelect . value = defaultChatSession ;
15661613 }
15671614
1568- if ( isOneTime && manualSessionEl && manualSessionEl . checked ) {
1569- manualSessionEl . checked = false ;
1615+ if ( isOneTime && runFirstEl && runFirstEl . checked ) {
1616+ runFirstEl . checked = false ;
15701617 }
15711618
1572- if ( isManualSession && oneTimeEl && oneTimeEl . checked ) {
1573- oneTimeEl . checked = false ;
1619+ updateOneTimeDelayPreview ( ) ;
1620+ }
1621+
1622+ function normalizeOneTimeDelayPart ( value , maxValue ) {
1623+ var numericValue = typeof value === "number" ? value : Number ( value ) ;
1624+ if ( ! isFinite ( numericValue ) || numericValue < 0 ) {
1625+ return 0 ;
1626+ }
1627+
1628+ var wholeNumber = Math . floor ( numericValue ) ;
1629+ if ( typeof maxValue === "number" ) {
1630+ return Math . min ( wholeNumber , maxValue ) ;
1631+ }
1632+ return wholeNumber ;
1633+ }
1634+
1635+ function getOneTimeDelaySecondsFromInputs ( ) {
1636+ return normalizeOneTimeDelayPart ( oneTimeDelayHours ? oneTimeDelayHours . value : 0 ) * 3600
1637+ + normalizeOneTimeDelayPart ( oneTimeDelayMinutes ? oneTimeDelayMinutes . value : 0 , 59 ) * 60
1638+ + normalizeOneTimeDelayPart ( oneTimeDelaySeconds ? oneTimeDelaySeconds . value : 0 , 59 ) ;
1639+ }
1640+
1641+ function formatHumanDuration ( totalSeconds ) {
1642+ var normalizedSeconds = normalizeOneTimeDelayPart ( totalSeconds ) ;
1643+ var hours = Math . floor ( normalizedSeconds / 3600 ) ;
1644+ var minutes = Math . floor ( ( normalizedSeconds % 3600 ) / 60 ) ;
1645+ var seconds = normalizedSeconds % 60 ;
1646+ if ( hours > 0 ) {
1647+ return minutes > 0
1648+ ? hours + " " + ( hours === 1 ? "hour" : "hours" ) + " " + minutes + " " + ( minutes === 1 ? "minute" : "minutes" )
1649+ : hours + " " + ( hours === 1 ? "hour" : "hours" ) ;
1650+ }
1651+ if ( minutes > 0 ) {
1652+ return seconds > 0
1653+ ? minutes + " " + ( minutes === 1 ? "minute" : "minutes" ) + " " + seconds + " " + ( seconds === 1 ? "second" : "seconds" )
1654+ : minutes + " " + ( minutes === 1 ? "minute" : "minutes" ) ;
15741655 }
1656+ return normalizedSeconds + " " + ( normalizedSeconds === 1 ? "second" : "seconds" ) ;
1657+ }
1658+
1659+ function setOneTimeDelayInputs ( totalSeconds ) {
1660+ var normalized = normalizeOneTimeDelayPart ( totalSeconds ) ;
1661+ if ( oneTimeDelayHours ) {
1662+ oneTimeDelayHours . value = String ( Math . floor ( normalized / 3600 ) ) ;
1663+ }
1664+ if ( oneTimeDelayMinutes ) {
1665+ oneTimeDelayMinutes . value = String ( Math . floor ( ( normalized % 3600 ) / 60 ) ) ;
1666+ }
1667+ if ( oneTimeDelaySeconds ) {
1668+ oneTimeDelaySeconds . value = String ( normalized % 60 ) ;
1669+ }
1670+ }
1671+
1672+ function deriveTaskOneTimeDelaySeconds ( task ) {
1673+ var storedDelay = normalizeOneTimeDelayPart ( task && task . oneTimeDelaySeconds ) ;
1674+ if ( storedDelay > 0 ) {
1675+ return storedDelay ;
1676+ }
1677+ if ( ! ( task && task . oneTime === true && task . nextRun ) ) {
1678+ return 0 ;
1679+ }
1680+
1681+ var nextRunDate = new Date ( task . nextRun ) ;
1682+ var remainingSeconds = Math . ceil ( ( nextRunDate . getTime ( ) - Date . now ( ) ) / 1000 ) ;
1683+ return remainingSeconds > 0 ? remainingSeconds : 0 ;
1684+ }
1685+
1686+ function updateOneTimeDelayPreview ( ) {
1687+ if ( ! oneTimeDelayPreviewText ) {
1688+ return ;
1689+ }
1690+
1691+ var totalSeconds = getOneTimeDelaySecondsFromInputs ( ) ;
1692+ if ( totalSeconds < 1 ) {
1693+ oneTimeDelayPreviewText . textContent =
1694+ strings . oneTimeDelayPreviewUnset || "Set a delay to schedule this one-time run." ;
1695+ return ;
1696+ }
1697+
1698+ var nextRunDate = new Date ( Date . now ( ) + totalSeconds * 1000 ) ;
1699+ oneTimeDelayPreviewText . textContent =
1700+ formatHumanDuration ( totalSeconds ) +
1701+ " " + ( strings . oneTimeDelayFromNow || "from now" ) +
1702+ " • " + nextRunDate . toLocaleString ( locale ) ;
15751703 }
15761704
15771705 function formatHistoryLabel ( entry ) {
@@ -3051,14 +3179,23 @@ syncTodoLabelSuggestions();
30513179 strings . boardCommentEditHint || "Add a focused update without rewriting the full description."
30523180 ) ;
30533181 }
3054- return comments . map ( function ( comment , commentIndex ) {
3055- var sourceLabel = getTodoCommentSourceLabel ( comment . source || "human-form" ) ;
3182+ return comments . slice ( ) . reverse ( ) . map ( function ( comment , reverseIndex ) {
3183+ var source = comment && comment . source ? String ( comment . source ) : "human-form" ;
3184+ var commentIndex = comments . length - reverseIndex - 1 ;
3185+ var sourceLabel = getTodoCommentSourceLabel ( source ) ;
30563186 var sequence = typeof comment . sequence === "number" ? comment . sequence : 1 ;
30573187 var displayDate = comment . updatedAt || comment . editedAt || comment . createdAt ;
30583188 var toneClass = getTodoCommentToneClass ( comment ) ;
3059- var userFormClass = comment . source === "human-form" && String ( comment . author || "" ) . toLowerCase ( ) === "user"
3189+ var userFormClass = source === "human-form" && String ( comment . author || "" ) . toLowerCase ( ) === "user"
30603190 ? " is-user-form"
30613191 : "" ;
3192+ var rawBody = String ( comment . body || "" ) ;
3193+ var previewBody = source === "system-event"
3194+ ? rawBody . replace ( / \s + / g, " " ) . trim ( )
3195+ : rawBody ;
3196+ if ( source === "system-event" && previewBody . length > 140 ) {
3197+ previewBody = previewBody . slice ( 0 , 137 ) + "..." ;
3198+ }
30623199 return '<article class="todo-comment-card' + toneClass + userFormClass + '" data-comment-index="' + escapeAttr ( String ( commentIndex ) ) + '" tabindex="0" role="button" aria-label="' + escapeAttr ( strings . boardCommentOpenFull || "Open full comment" ) + '">' +
30633200 '<div class="todo-comment-header">' +
30643201 '<div class="todo-comment-heading">' +
@@ -3071,7 +3208,7 @@ syncTodoLabelSuggestions();
30713208 '</div>' +
30723209 '</div>' +
30733210 '<div class="note todo-comment-author">' + escapeHtml ( comment . author || "system" ) + '</div>' +
3074- '<div class="todo-comment-body">' + escapeHtml ( comment . body || "" ) + '</div>' +
3211+ '<div class="todo-comment-body">' + escapeHtml ( previewBody ) + '</div>' +
30753212 '<div class="todo-comment-expand-hint">' + escapeHtml ( strings . boardCommentOpenFull || "Open full comment" ) + '</div>' +
30763213 '</article>' ;
30773214 } ) . join ( "" ) ;
@@ -4699,6 +4836,7 @@ syncTodoLabelSuggestions();
46994836 name : taskNameEl ? String ( taskNameEl . value || "" ) : "" ,
47004837 prompt : promptTextEl ? String ( promptTextEl . value || "" ) : "" ,
47014838 cronExpression : cronExpression ? String ( cronExpression . value || "" ) : "" ,
4839+ oneTimeDelaySeconds : getOneTimeDelaySecondsFromInputs ( ) ,
47024840 labels : normalizeTaskLabelsValue ( taskLabelsInput ? taskLabelsInput . value : "" ) ,
47034841 agent : agentValue ,
47044842 model : modelValue ,
@@ -4720,6 +4858,7 @@ syncTodoLabelSuggestions();
47204858 name : String ( task . name || "" ) ,
47214859 prompt : typeof task . prompt === "string" ? task . prompt : "" ,
47224860 cronExpression : String ( task . cronExpression || "" ) ,
4861+ oneTimeDelaySeconds : deriveTaskOneTimeDelaySeconds ( task ) ,
47234862 labels : normalizeTaskLabelsValue ( toLabelString ( task . labels ) ) ,
47244863 agent : String ( task . agent || "" ) ,
47254864 model : String ( task . model || "" ) ,
@@ -5208,6 +5347,7 @@ syncTodoLabelSuggestions();
52085347 if ( ! isPersistedTabName ( tabName ) ) {
52095348 tabName = "help" ;
52105349 }
5350+ var shouldRefreshStorageStatus = tabName === "settings" && activeTabName !== "settings" ;
52115351 if ( activeTabName ) {
52125352 captureTabScrollPosition ( activeTabName ) ;
52135353 }
@@ -5226,6 +5366,9 @@ syncTodoLabelSuggestions();
52265366 restoreTabScrollPosition ( tabName ) ;
52275367 updateBoardAutoCollapseFromScroll ( true ) ;
52285368 scheduleBoardStickyMetrics ( ) ;
5369+ if ( shouldRefreshStorageStatus ) {
5370+ vscode . postMessage ( { type : "refreshStorageStatus" } ) ;
5371+ }
52295372 maybePlayInitialHelpWarp ( tabName ) ;
52305373 }
52315374
@@ -5260,6 +5403,12 @@ syncTodoLabelSuggestions();
52605403 bindGenericChange ( manualSessionToggle , function ( ) {
52615404 syncRecurringChatSessionUi ( ) ;
52625405 } ) ;
5406+ [ oneTimeDelayHours , oneTimeDelayMinutes , oneTimeDelaySeconds ] . forEach ( function ( control ) {
5407+ bindGenericChange ( control , function ( ) {
5408+ updateOneTimeDelayPreview ( ) ;
5409+ syncEditorTabLabels ( ) ;
5410+ } ) ;
5411+ } ) ;
52635412
52645413 bindTaskFilterBar ( taskFilterBar , {
52655414 syncTaskFilterButtons : syncTaskFilterButtons ,
@@ -5373,6 +5522,32 @@ syncTodoLabelSuggestions();
53735522 "jobs-friendly-frequency" : function ( ) {
53745523 refreshJobsFriendlyCronFromBuilder ( ) ;
53755524 } ,
5525+ "one-time-delay-hours" : function ( ) {
5526+ updateOneTimeDelayPreview ( ) ;
5527+ } ,
5528+ "one-time-delay-minutes" : function ( ) {
5529+ updateOneTimeDelayPreview ( ) ;
5530+ } ,
5531+ "one-time-delay-seconds" : function ( ) {
5532+ updateOneTimeDelayPreview ( ) ;
5533+ } ,
5534+ } ) ;
5535+
5536+ document . addEventListener ( "click" , function ( event ) {
5537+ var target = event && event . target && event . target . nodeType === 3
5538+ ? event . target . parentElement
5539+ : event . target ;
5540+ if ( ! target || typeof target . closest !== "function" ) {
5541+ return ;
5542+ }
5543+ var presetButton = target . closest ( ".one-time-delay-preset" ) ;
5544+ if ( ! presetButton ) {
5545+ return ;
5546+ }
5547+ event . preventDefault ( ) ;
5548+ setOneTimeDelayInputs ( presetButton . getAttribute ( "data-seconds" ) ) ;
5549+ updateOneTimeDelayPreview ( ) ;
5550+ syncEditorTabLabels ( ) ;
53765551 } ) ;
53775552
53785553 bindFriendlyCronBuilderAutoUpdate ( {
@@ -5715,6 +5890,7 @@ syncTodoLabelSuggestions();
57155890 syncBundledAgents : syncBundledAgentsBtn ,
57165891 openCopilotSettings : openCopilotSettingsBtn ,
57175892 openExtensionSettings : openExtensionSettingsBtn ,
5893+ refreshStorageStatus : refreshStorageStatusBtn ,
57185894 importStorageFromJson : importStorageFromJsonBtn ,
57195895 exportStorageToJson : exportStorageToJsonBtn ,
57205896 } ) ;
@@ -7108,7 +7284,7 @@ syncTodoLabelSuggestions();
71087284 }
71097285
71107286 function refreshTaskEditorDerivedState ( ) {
7111- [ syncRecurringChatSessionUi , updateFriendlyVisibility , updateCronPreview ] . forEach ( function ( refreshFn ) {
7287+ [ syncRecurringChatSessionUi , updateFriendlyVisibility , updateCronPreview , updateOneTimeDelayPreview ] . forEach ( function ( refreshFn ) {
71127288 refreshFn ( ) ;
71137289 } ) ;
71147290 }
@@ -7290,6 +7466,7 @@ syncTodoLabelSuggestions();
72907466 if ( jitterSecondsInput ) {
72917467 jitterSecondsInput . value = String ( defaultJitterSeconds ) ;
72927468 }
7469+ setOneTimeDelayInputs ( 0 ) ;
72937470 if ( taskLabelsInput ) {
72947471 taskLabelsInput . value = "" ;
72957472 }
@@ -7358,6 +7535,7 @@ syncTodoLabelSuggestions();
73587535 if ( jitterSecondsInput ) {
73597536 jitterSecondsInput . value = String ( task . jitterSeconds ?? defaultJitterSeconds ) ;
73607537 }
7538+ setOneTimeDelayInputs ( deriveTaskOneTimeDelaySeconds ( task ) ) ;
73617539
73627540 var runFirstEl = document . getElementById ( "run-first" ) ;
73637541 if ( runFirstEl ) runFirstEl . checked = false ;
@@ -8199,6 +8377,9 @@ syncTodoLabelSuggestions();
81998377 "#model-select" ,
82008378 "#template-select" ,
82018379 "#jitter-seconds" ,
8380+ "#one-time-delay-hours" ,
8381+ "#one-time-delay-minutes" ,
8382+ "#one-time-delay-seconds" ,
82028383 "#chat-session" ,
82038384 "#run-first" ,
82048385 "#one-time" ,
@@ -8793,6 +8974,7 @@ syncTodoLabelSuggestions();
87938974 case "updateStorageSettings" :
87948975 storageSettings = normalizeStorageSettings ( message . storageSettings , storageSettings ) ;
87958976 renderStorageSettingsControls ( ) ;
8977+ showStorageStatusRefreshNote ( ) ;
87968978 break ;
87978979 case "updateApprovalMode" :
87988980 if ( approvalModeSelect && message . approvalMode ) {
0 commit comments