'use strict'; // Fixed native operations only. A model never supplies URLs, scripts or handles. function createLibraryOrganization({capture,assertCurrent,propose,commit,applyTabs,inspect,audit,changed=()=>{},autoAllowed=()=>false}={}){ if([capture,assertCurrent,propose,commit,applyTabs,inspect,audit].some(fn=>typeof fn!=='function'))throw TypeError('Invalid library callbacks'); let busy=false;const proposals=new Map(); const checkInput=input=>{if(!input||typeof input!=='object'||Array.isArray(input)||Object.keys(input).some(k=>!['proposalId','confirm','autoApply'].includes(k))||typeof input.proposalId!=='string'||input.proposalId.length>128)throw Error('Invalid organization request');}; async function plan({signal}={}){ if(busy)throw Error('Library organization is already running');busy=true;changed(); try{ const current=snapshot=>{if(signal?.aborted)throw Error('Organization action cancelled');assertCurrent(snapshot);}; const snapshot=await capture();current(snapshot);await inspect(snapshot);current(snapshot); const result=await propose(snapshot);current(snapshot); if(typeof result?.proposalId!=='string'||result.proposalId.length>128)throw Error('Invalid library proposal'); if(snapshot.snapshotHash&&(result.binding?.snapshotHash!==snapshot.snapshotHash||result.binding?.policyVersion!==snapshot.policyVersion||result.binding?.bookmarkRevision!==snapshot.bookmarkRevision))throw Error('Invalid organization proposal binding'); const core=await import('../../packages/core/library.mjs'); const validated=core.validateLibraryPlan(result.plan,{bookmarks:snapshot.bookmarks,tabs:snapshot.tabs}); // Keep authoritative plan and binding in native memory, never renderer input. proposals.clear();proposals.set(result.proposalId,{snapshot,plan:validated,expires:Date.now()+300000}); await audit('library.ai.proposed',`bookmarks-${validated.bookmarks.length}:tabs-${validated.tabs.length}`); return {...result,plan:structuredClone(validated)}; }finally{busy=false;changed();} } async function apply(input,{signal}={}){ checkInput(input);if(busy)throw Error('Library organization is already running'); const stored=proposals.get(input.proposalId); if(!stored||stored.expires<=Date.now())throw Error('Organization proposal expired. Generate a fresh proposal.'); if(input.confirm!==true&&!(input.autoApply===true&&autoAllowed()))throw Error('Review and confirm the organization proposal'); if(signal?.aborted)throw Error('Organization action cancelled');assertCurrent(stored.snapshot);busy=true;changed(); let committed=false; try{ const result=await commit(input.proposalId,stored.snapshot,input.autoApply===true);committed=true; // Bookmarks are committed centrally. Navigation or policy changes can occur // meanwhile: never silently apply stale tab assignments after such a change. if(signal?.aborted)throw Error('Organization action cancelled');assertCurrent(stored.snapshot,{afterCommit:true,result}); applyTabs(stored.plan.tabs,result);proposals.delete(input.proposalId); await audit('library.ai.applied',`bookmarks-${stored.plan.bookmarks.length}:tabs-${stored.plan.tabs.length}`); return {completed:true,bookmarkRevision:result.revision,tabCount:stored.plan.tabs.length}; }catch(error){ // eslint-disable-next-line preserve-caught-error -- Expose only a fixed sanitized error; the original cause may contain sensitive paths or data. if(committed){proposals.delete(input.proposalId);await audit('library.ai.reconcile','central-bookmarks-committed');throw Error('Bookmarks were organized centrally; tabs were unchanged because the browser context changed. Refresh the library.');} throw error; }finally{busy=false;changed();} } return {plan,apply,isBusy:()=>busy}; } module.exports={createLibraryOrganization};