@@ -30,6 +30,7 @@ var MAX_PENDING_CHANGES = 10;
3030var MAX_ADDITIONAL_CONTEXT = 8 ;
3131var STREAM_DELAY_MS = 30 ;
3232var PREVIEW_MAX_LINES = 120 ;
33+ var RECOMMENDED_MODELS = [ "gpt-5.3-codex" , "gpt-4.1" , "gpt-4o-mini" ] ;
3334var AUTH_ORDER = [ "logged-in" , "no-entitlement" , "token-expired" , "offline" ] ;
3435function createId ( prefix ) {
3536 return `${ prefix } -${ Date . now ( ) } -${ Math . random ( ) . toString ( 36 ) . slice ( 2 , 8 ) } ` ;
@@ -57,6 +58,9 @@ function defaultSettings() {
5758 authCheckedAt : Date . now ( ) ,
5859 model : "gpt-5.3-codex" ,
5960 contextPolicy : "selection-first" ,
61+ changeApplyPolicy : "confirm-write" ,
62+ retryFailedPrompt : true ,
63+ lastFailedPrompt : "" ,
6064 debugLogging : false
6165 } ;
6266}
@@ -66,6 +70,9 @@ function isAuthState(value) {
6670function isContextPolicy ( value ) {
6771 return value === "selection-first" || value === "note-only" ;
6872}
73+ function isChangeApplyPolicy ( value ) {
74+ return value === "confirm-write" || value === "auto-apply" ;
75+ }
6976function isMessageRole ( value ) {
7077 return value === "user" || value === "assistant" || value === "system" ;
7178}
@@ -175,6 +182,9 @@ function normalizeSettings(raw) {
175182 authCheckedAt : typeof source . authCheckedAt === "number" ? source . authCheckedAt : fallback . authCheckedAt ,
176183 model : typeof source . model === "string" && source . model . trim ( ) . length > 0 ? source . model : fallback . model ,
177184 contextPolicy : isContextPolicy ( source . contextPolicy ) ? source . contextPolicy : fallback . contextPolicy ,
185+ changeApplyPolicy : isChangeApplyPolicy ( source . changeApplyPolicy ) ? source . changeApplyPolicy : fallback . changeApplyPolicy ,
186+ retryFailedPrompt : typeof source . retryFailedPrompt === "boolean" ? source . retryFailedPrompt : fallback . retryFailedPrompt ,
187+ lastFailedPrompt : typeof source . lastFailedPrompt === "string" ? source . lastFailedPrompt : fallback . lastFailedPrompt ,
178188 debugLogging : Boolean ( source . debugLogging )
179189 } ;
180190}
@@ -220,10 +230,16 @@ var CopilotSidebarView = class extends import_obsidian.ItemView {
220230 text : "Refresh Auth" ,
221231 cls : "copilot-button"
222232 } ) ;
233+ const retryFailedButton = controlRow . createEl ( "button" , {
234+ text : "Retry Failed" ,
235+ cls : "copilot-button"
236+ } ) ;
223237 const layout = root . createDiv ( { cls : "copilot-sidebar-layout" } ) ;
224238 const leftPane = layout . createDiv ( { cls : "copilot-sidebar-pane copilot-sidebar-pane-sessions" } ) ;
225239 leftPane . createDiv ( { text : "Sessions" , cls : "copilot-pane-title" } ) ;
226240 const sessionList = leftPane . createDiv ( { cls : "copilot-session-list" } ) ;
241+ leftPane . createDiv ( { text : "Settings" , cls : "copilot-pane-title" } ) ;
242+ const settingsPanel = leftPane . createDiv ( { cls : "copilot-settings-panel" } ) ;
227243 leftPane . createDiv ( { text : "Context Notes" , cls : "copilot-pane-title" } ) ;
228244 const contextList = leftPane . createDiv ( { cls : "copilot-context-list" } ) ;
229245 leftPane . createDiv ( { text : "Pending Changes" , cls : "copilot-pane-title" } ) ;
@@ -255,6 +271,9 @@ var CopilotSidebarView = class extends import_obsidian.ItemView {
255271 refreshAuthButton . addEventListener ( "click" , ( ) => {
256272 void this . plugin . refreshAuthStatus ( "manual" ) ;
257273 } ) ;
274+ retryFailedButton . addEventListener ( "click" , ( ) => {
275+ void this . plugin . retryLastFailedPrompt ( ) ;
276+ } ) ;
258277 composerButton . addEventListener ( "click" , ( ) => {
259278 void this . submitComposer ( ) ;
260279 } ) ;
@@ -269,6 +288,7 @@ var CopilotSidebarView = class extends import_obsidian.ItemView {
269288 authBadge,
270289 authMeta : authDetail ,
271290 sessionList,
291+ settingsPanel,
272292 contextList,
273293 pendingList,
274294 messages,
@@ -295,6 +315,7 @@ var CopilotSidebarView = class extends import_obsidian.ItemView {
295315 const compactDetail = snapshot . authDetail . length > 180 ? `${ snapshot . authDetail . slice ( 0 , 177 ) } ...` : snapshot . authDetail ;
296316 this . elements . authMeta . setText ( `${ compactDetail } | checked ${ checkedAt } ` ) ;
297317 this . renderSessions ( snapshot ) ;
318+ this . renderSettings ( snapshot ) ;
298319 this . renderContextNotes ( snapshot ) ;
299320 this . renderPendingChanges ( snapshot ) ;
300321 this . renderMessages ( activeSession ) ;
@@ -328,6 +349,81 @@ var CopilotSidebarView = class extends import_obsidian.ItemView {
328349 } ) ;
329350 }
330351 }
352+ renderSettings ( snapshot ) {
353+ if ( ! this . elements ) {
354+ return ;
355+ }
356+ const panel = this . elements . settingsPanel ;
357+ panel . empty ( ) ;
358+ const modelRow = panel . createDiv ( { cls : "copilot-setting-row" } ) ;
359+ modelRow . createDiv ( { text : "Model" , cls : "copilot-setting-label" } ) ;
360+ const modelSelect = modelRow . createEl ( "select" , { cls : "copilot-setting-select" } ) ;
361+ for ( const model of RECOMMENDED_MODELS ) {
362+ const option = modelSelect . createEl ( "option" ) ;
363+ option . value = model ;
364+ option . text = model ;
365+ }
366+ if ( ! RECOMMENDED_MODELS . includes ( snapshot . model ) ) {
367+ const customOption = modelSelect . createEl ( "option" ) ;
368+ customOption . value = snapshot . model ;
369+ customOption . text = `${ snapshot . model } (custom)` ;
370+ }
371+ modelSelect . value = snapshot . model ;
372+ modelSelect . addEventListener ( "change" , ( ) => {
373+ void this . plugin . updateModel ( modelSelect . value ) ;
374+ } ) ;
375+ const contextRow = panel . createDiv ( { cls : "copilot-setting-row" } ) ;
376+ contextRow . createDiv ( { text : "Context Policy" , cls : "copilot-setting-label" } ) ;
377+ const contextSelect = contextRow . createEl ( "select" , { cls : "copilot-setting-select" } ) ;
378+ const selectionOption = contextSelect . createEl ( "option" ) ;
379+ selectionOption . value = "selection-first" ;
380+ selectionOption . text = "Selection First" ;
381+ const noteOption = contextSelect . createEl ( "option" ) ;
382+ noteOption . value = "note-only" ;
383+ noteOption . text = "Note Only" ;
384+ contextSelect . value = snapshot . contextPolicy ;
385+ contextSelect . addEventListener ( "change" , ( ) => {
386+ const nextPolicy = contextSelect . value === "note-only" ? "note-only" : "selection-first" ;
387+ void this . plugin . updateContextPolicy ( nextPolicy ) ;
388+ } ) ;
389+ const applyPolicyRow = panel . createDiv ( { cls : "copilot-setting-row" } ) ;
390+ applyPolicyRow . createDiv ( { text : "Write Policy" , cls : "copilot-setting-label" } ) ;
391+ const applyPolicySelect = applyPolicyRow . createEl ( "select" , { cls : "copilot-setting-select" } ) ;
392+ const confirmOption = applyPolicySelect . createEl ( "option" ) ;
393+ confirmOption . value = "confirm-write" ;
394+ confirmOption . text = "Confirm Before Apply" ;
395+ const autoOption = applyPolicySelect . createEl ( "option" ) ;
396+ autoOption . value = "auto-apply" ;
397+ autoOption . text = "Auto Apply" ;
398+ applyPolicySelect . value = snapshot . changeApplyPolicy ;
399+ applyPolicySelect . addEventListener ( "change" , ( ) => {
400+ const nextPolicy = applyPolicySelect . value === "auto-apply" ? "auto-apply" : "confirm-write" ;
401+ void this . plugin . updateChangeApplyPolicy ( nextPolicy ) ;
402+ } ) ;
403+ const debugRow = panel . createDiv ( { cls : "copilot-setting-row" } ) ;
404+ const debugToggle = debugRow . createEl ( "input" , {
405+ cls : "copilot-setting-checkbox" ,
406+ attr : { type : "checkbox" }
407+ } ) ;
408+ debugToggle . checked = snapshot . debugLogging ;
409+ debugRow . createDiv ( { text : "Debug logging" , cls : "copilot-setting-label" } ) ;
410+ debugToggle . addEventListener ( "change" , ( ) => {
411+ void this . plugin . updateDebugLogging ( debugToggle . checked ) ;
412+ } ) ;
413+ const retryRow = panel . createDiv ( { cls : "copilot-setting-row" } ) ;
414+ const retryToggle = retryRow . createEl ( "input" , {
415+ cls : "copilot-setting-checkbox" ,
416+ attr : { type : "checkbox" }
417+ } ) ;
418+ retryToggle . checked = snapshot . retryFailedPrompt ;
419+ retryRow . createDiv ( { text : "Enable failed prompt retry" , cls : "copilot-setting-label" } ) ;
420+ retryToggle . addEventListener ( "change" , ( ) => {
421+ void this . plugin . updateRetryFailedPrompt ( retryToggle . checked ) ;
422+ } ) ;
423+ const failedPrompt = snapshot . lastFailedPrompt . trim ( ) ;
424+ const failedText = failedPrompt . length > 0 ? failedPrompt . slice ( 0 , 120 ) : "None" ;
425+ panel . createDiv ( { text : `Last failed prompt: ${ failedText } ` , cls : "copilot-setting-hint" } ) ;
426+ }
331427 renderPendingChanges ( snapshot ) {
332428 if ( ! this . elements ) {
333429 return ;
@@ -515,6 +611,21 @@ var CopilotSidebarPlugin = class extends import_obsidian.Plugin {
515611 await this . undoLastAppliedChange ( ) ;
516612 }
517613 } ) ;
614+ this . addCommand ( {
615+ id : "open-sidebar-settings-panel" ,
616+ name : "Open sidebar settings panel" ,
617+ callback : async ( ) => {
618+ await this . activateView ( ) ;
619+ new import_obsidian . Notice ( "Open the Copilot Sidebar and use the Settings section." ) ;
620+ }
621+ } ) ;
622+ this . addCommand ( {
623+ id : "retry-last-failed-prompt" ,
624+ name : "Retry last failed prompt" ,
625+ callback : async ( ) => {
626+ await this . retryLastFailedPrompt ( ) ;
627+ }
628+ } ) ;
518629 this . addCommand ( {
519630 id : "refresh-auth-status" ,
520631 name : "Refresh auth status" ,
@@ -553,6 +664,11 @@ var CopilotSidebarPlugin = class extends import_obsidian.Plugin {
553664 authDetail : this . settings . authDetail ,
554665 authCheckedAt : this . settings . authCheckedAt ,
555666 model : this . settings . model ,
667+ contextPolicy : this . settings . contextPolicy ,
668+ changeApplyPolicy : this . settings . changeApplyPolicy ,
669+ retryFailedPrompt : this . settings . retryFailedPrompt ,
670+ lastFailedPrompt : this . settings . lastFailedPrompt ,
671+ debugLogging : this . settings . debugLogging ,
556672 isStreaming : this . streaming
557673 } ;
558674 }
@@ -566,6 +682,30 @@ var CopilotSidebarPlugin = class extends import_obsidian.Plugin {
566682 this . settings . activeSessionId = sessionId ;
567683 await this . persistAndRender ( ) ;
568684 }
685+ async updateModel ( model ) {
686+ const trimmed = model . trim ( ) ;
687+ if ( ! trimmed ) {
688+ return ;
689+ }
690+ this . settings . model = trimmed ;
691+ await this . persistAndRender ( ) ;
692+ }
693+ async updateContextPolicy ( contextPolicy ) {
694+ this . settings . contextPolicy = contextPolicy ;
695+ await this . persistAndRender ( ) ;
696+ }
697+ async updateChangeApplyPolicy ( changeApplyPolicy ) {
698+ this . settings . changeApplyPolicy = changeApplyPolicy ;
699+ await this . persistAndRender ( ) ;
700+ }
701+ async updateRetryFailedPrompt ( enabled ) {
702+ this . settings . retryFailedPrompt = enabled ;
703+ await this . persistAndRender ( ) ;
704+ }
705+ async updateDebugLogging ( enabled ) {
706+ this . settings . debugLogging = enabled ;
707+ await this . persistAndRender ( ) ;
708+ }
569709 async startNewSession ( ) {
570710 const session = createSession ( ) ;
571711 this . settings . sessions = [ session , ...this . settings . sessions ] . slice ( 0 , MAX_SESSIONS ) ;
@@ -758,6 +898,13 @@ var CopilotSidebarPlugin = class extends import_obsidian.Plugin {
758898 await this . persistAndRender ( ) ;
759899 return ;
760900 }
901+ if ( this . settings . changeApplyPolicy === "confirm-write" ) {
902+ const accepted = typeof window !== "undefined" ? window . confirm ( `Apply pending change to ${ change . notePath } ?` ) : true ;
903+ if ( ! accepted ) {
904+ new import_obsidian . Notice ( "Apply canceled by policy confirmation." ) ;
905+ return ;
906+ }
907+ }
761908 await this . app . vault . modify ( target , change . after ) ;
762909 this . settings . lastAppliedChange = {
763910 ...change ,
@@ -805,11 +952,35 @@ var CopilotSidebarPlugin = class extends import_obsidian.Plugin {
805952 }
806953 await this . applyPendingChange ( first . id ) ;
807954 }
955+ async retryLastFailedPrompt ( ) {
956+ const lastFailed = this . settings . lastFailedPrompt . trim ( ) ;
957+ if ( ! lastFailed ) {
958+ new import_obsidian . Notice ( "No failed prompt to retry." ) ;
959+ return ;
960+ }
961+ await this . refreshAuthStatus ( "manual" ) ;
962+ if ( this . settings . authState !== "logged-in" ) {
963+ new import_obsidian . Notice ( `Cannot retry while auth is ${ this . settings . authState } .` ) ;
964+ return ;
965+ }
966+ await this . sendUserMessage ( lastFailed ) ;
967+ }
808968 async sendUserMessage ( prompt , providedContext ) {
809969 const trimmed = prompt . trim ( ) ;
810970 if ( ! trimmed ) {
811971 return ;
812972 }
973+ if ( this . settings . retryFailedPrompt && this . settings . authState !== "logged-in" ) {
974+ this . settings . lastFailedPrompt = trimmed ;
975+ }
976+ if ( this . settings . debugLogging ) {
977+ console . info ( "[copilot-sidebar] sendUserMessage" , {
978+ authState : this . settings . authState ,
979+ promptLength : trimmed . length ,
980+ contextPolicy : this . settings . contextPolicy ,
981+ changeApplyPolicy : this . settings . changeApplyPolicy
982+ } ) ;
983+ }
813984 const session = this . ensureActiveSession ( ) ;
814985 session . messages . push ( {
815986 id : createId ( "msg-user" ) ,
@@ -842,6 +1013,9 @@ var CopilotSidebarPlugin = class extends import_obsidian.Plugin {
8421013 assistantMessage . streaming = false ;
8431014 session . updatedAt = Date . now ( ) ;
8441015 this . streaming = false ;
1016+ if ( this . settings . authState === "logged-in" ) {
1017+ this . settings . lastFailedPrompt = "" ;
1018+ }
8451019 await this . maybeCreatePendingChange ( trimmed , response , context ) ;
8461020 this . syncSelectedPendingChange ( ) ;
8471021 await this . persistAndRender ( ) ;
@@ -947,6 +1121,7 @@ var CopilotSidebarPlugin = class extends import_obsidian.Plugin {
9471121 `Prompt: ${ prompt } ` ,
9481122 `Primary context (${ this . settings . contextPolicy } ): ${ primaryContext } ` ,
9491123 `Secondary context: ${ secondaryContext } ` ,
1124+ `Write policy: ${ this . settings . changeApplyPolicy } ` ,
9501125 `Merged additional context: ${ additionalSummary } ` ,
9511126 "Suggested next steps:" ,
9521127 "1. Validate key claims in your note." ,
@@ -955,6 +1130,9 @@ var CopilotSidebarPlugin = class extends import_obsidian.Plugin {
9551130 ] . join ( " " ) ;
9561131 }
9571132 async maybeCreatePendingChange ( prompt , response , context ) {
1133+ if ( this . settings . authState !== "logged-in" ) {
1134+ return ;
1135+ }
9581136 if ( ! context . notePath || ! context . noteContent ) {
9591137 return ;
9601138 }
0 commit comments