opencode-navigator 0.21.8 → 0.22.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/server.js CHANGED
@@ -6,30 +6,6 @@ const statuses=new Set(["pending","in_progress","completed","cancelled"])
6
6
  const priorities=new Set(["high","medium","low"])
7
7
  const todos=Array.isArray(state.todos)?state.todos.filter((item)=>item&&typeof item.content==="string"&&item.content.trim().length>0&&item.content.length<=512&&statuses.has(item.status)).slice(0,100).map((item)=>({content:item.content.trim(),status:item.status,...priorities.has(item.priority??"")&&{priority:item.priority}})):[]
8
8
  return{todos,guidance:state.guidance===true}}
9
- function todoGuidance(state){const lines=state.todos.map((todo)=>`- [${todo.status}] ${todo.content}`)
10
- return ["Navigator Todo: when work has multiple steps, use navigator_todo_write to keep the session Todo list current.","Mark work in progress, completed, or cancelled as it changes; avoid claiming unfinished work is done.",...lines.length>0?["Current Navigator Todo items:",...lines]:[]].join(`
11
- `)
12
- }
13
- function key(sessionID){return `session.todo.${sessionID}`
14
- }
15
- function createManagedTodos(storage,onChange){const pending=new Map
16
- async function list(sessionID){return normalizeTodoState(await storage.get(key(sessionID)))
17
- }
18
- function change(sessionID,apply){const request=(pending.get(sessionID)??Promise.resolve()).catch(()=>{}).then(async ()=>{const next=apply(await list(sessionID))
19
- await storage.set(key(sessionID),next)
20
- await onChange(sessionID,next)
21
- return next
22
- })
23
- pending.set(sessionID,request)
24
- const clear=()=>{if(pending.get(sessionID)===request)
25
- pending.delete(sessionID)
26
- }
27
- request.then(clear).catch(clear)
28
- return request
29
- }
30
- return{list,replace(sessionID,todos){return change(sessionID,(current)=>({...current,todos:normalizeTodoState({todos}).todos}))
31
- },guidance(sessionID,enabled){return change(sessionID,(current)=>({...current,guidance:enabled}))
32
- }}}
33
9
  var item={type:"object",properties:{content:{type:"string",minLength:1,maxLength:512},status:{type:"string",enum:["pending","in_progress","completed","cancelled"]},priority:{type:"string",enum:["high","medium","low"]}},required:["content","status"],additionalProperties:false
34
10
  }
35
11
  var todos={type:"array",items:item,maxItems:100}
@@ -43,6 +19,9 @@ var NavigatorTodoRpc={id:"opencode-navigator.todo",methods:{list:{input:{type:"o
43
19
  },output
44
20
  }},events:{updated:{schema:{type:"object",properties:{sessionID,todos,guidance:{type:"boolean"}},required:["sessionID","todos","guidance"],additionalProperties:false
45
21
  }}}};
22
+ var LegacyTodoArchiveRpc={id:NavigatorTodoRpc.id,methods:{list:NavigatorTodoRpc.methods.list},events:{}}
23
+ async function readLegacyTodoArchive(get,sessionID){return normalizeTodoState(await get(`session.todo.${sessionID}`))
24
+ }
46
25
  var text={type:"string",minLength:1,maxLength:512}
47
26
  var model={type:"object",properties:{providerID:text,modelID:text,sessionID:text,connectionID:text,variant:text,force:{type:"boolean"}},required:["providerID","modelID","sessionID"],additionalProperties:false
48
27
  }
@@ -484,6 +463,10 @@ const windows=openRouterFreeWindow(body,input.model.modelID,input.apiModelID)
484
463
  const id=typeof data.organization_id==="string"?data.organization_id:undefined
485
464
  return{...balanceSnapshot(input.model,input.provider.name,balances,Date.now(),id),windows,...windows.length>0&&{availability:"ready"},message:data.is_management_key===true?undefined:"Key budget, not account balance. Reading account credits requires a management key."
486
465
  }}
466
+ function credentialToken(credential){if(!credential||credential.type==="external")
467
+ return
468
+ return credential.type==="oauth"?credential.access:credential.key
469
+ }
487
470
  async function nativeQuotaContext(host,model,signal,fetcher){const [provider,catalog]=await Promise.all([host.provider.get({providerID:model.providerID}),host.model.list()])
488
471
  const selected=catalog.data.find((entry)=>entry.providerID===model.providerID&&entry.id===model.modelID&&entry.enabled)
489
472
  if(!selected||!provider.data.integrationID||provider.data.activation==="disabled")
@@ -492,7 +475,7 @@ const connection=await host.integration.connection.active(provider.data.integrat
492
475
  if(!connection)
493
476
  return
494
477
  const credential=await host.integration.connection.resolve(connection)
495
- const token=credential?.type==="oauth"?credential.access:credential?.key
478
+ const token=credentialToken(credential)
496
479
  if(!credential||!token)
497
480
  return
498
481
  signal.throwIfAborted()
@@ -502,7 +485,7 @@ async function sameNativeConnection(host,input){const current=input.provider.int
502
485
  if(!current||connectionKey(current)!==input.connectionID)
503
486
  return false
504
487
  const credential=await host.integration.connection.resolve(current)
505
- const token=credential?.type==="oauth"?credential.access:credential?.key
488
+ const token=credentialToken(credential)
506
489
  return token===input.token
507
490
  }
508
491
  function requestUsesCredential(request,input){const url=new URL(request.url)
@@ -518,7 +501,7 @@ if(["x-api-key","api-key","x-goog-api-key"].some((header)=>request.headers.get(h
518
501
  return true
519
502
  return url.hostname==="generativelanguage.googleapis.com"&&url.searchParams.get("key")===input.token
520
503
  }
521
- function key2(input){return JSON.stringify([input.model.providerID,input.model.modelID,input.model.variant,input.connectionID,createHash("sha256").update(input.token).digest("hex")])
504
+ function key(input){return JSON.stringify([input.model.providerID,input.model.modelID,input.model.variant,input.connectionID,createHash("sha256").update(input.token).digest("hex")])
522
505
  }
523
506
  function modelForEvent(event){return{providerID:event.model.providerID,modelID:event.model.id,sessionID:event.sessionID}}
524
507
  function rejectedRead(model,error,input){const status=error instanceof QuotaReadError?error.status:undefined
@@ -540,7 +523,7 @@ if(input.canonical==="github-copilot")
540
523
  return copilot.read(input)
541
524
  return readNativeRest(input)
542
525
  }
543
- async function currentResult(input,force){const identity=key2(input)
526
+ async function currentResult(input,force){const identity=key(input)
544
527
  const headers=observed.get(identity)
545
528
  const existing=cached.get(identity)
546
529
  let result=!force&&existing&&Date.now()-existing.fetchedAt<30000?existing:undefined
@@ -561,13 +544,13 @@ const epoch=generation
561
544
  const observation=AbortSignal.any([lifecycle,AbortSignal.timeout(1000)])
562
545
  try{const input=await withinQuotaSignal(observation,()=>nativeQuotaContext(host,modelForEvent(event),observation,fetcher))
563
546
  if(input&&epoch===generation&&requestUsesCredential(event.request,input)&&await withinQuotaSignal(observation,()=>sameNativeConnection(host,input)))
564
- captured.set(event.request,{key:key2(input),generation:epoch})
547
+ captured.set(event.request,{key:key(input),generation:epoch})
565
548
  } catch{}},async response(event){const request=captured.get(event.request)
566
549
  if(!request||request.generation!==generation||lifecycle.aborted)
567
550
  return false
568
551
  const observation=AbortSignal.any([lifecycle,AbortSignal.timeout(1000)])
569
552
  try{const input=await withinQuotaSignal(observation,()=>nativeQuotaContext(host,modelForEvent(event),observation,fetcher))
570
- if(!input||key2(input)!==request.key||!await withinQuotaSignal(observation,()=>sameNativeConnection(host,input)))
553
+ if(!input||key(input)!==request.key||!await withinQuotaSignal(observation,()=>sameNativeConnection(host,input)))
571
554
  return false
572
555
  let windows=nativeHeaderWindows(input.canonical,input.model.modelID,event.response.headers)
573
556
  if(windows.length===0&&event.response.status===429&&["google","google-vertex"].includes(input.canonical)&&event.response.headers.get("content-type")?.includes("json"))
@@ -629,32 +612,11 @@ await response.dispose()
629
612
  await rpc.dispose()
630
613
  await service.dispose()
631
614
  }}
632
- var input={type:"object",properties:{todos:NavigatorTodoRpc.methods.replace.input.properties.todos
633
- },required:["todos"],additionalProperties:false
634
- }
635
- var noNotify=async ()=>{}
636
- var todo_server_default=Plugin.define({id:"opencode-navigator.todo-server",async setup(host){let notify=noNotify
637
- const todos=createManagedTodos({get:(key)=>host.storage.get(key),set:(key,value)=>host.storage.set(key,value)
638
- },(sessionID,state)=>notify(sessionID,state))
639
- const rpc=await host.rpc.register(NavigatorTodoRpc,{list:async (value)=>todos.list(value.sessionID),replace:async (value)=>{const{sessionID,todos:items}=value
640
- return todos.replace(sessionID,items)
641
- },guidance:async (value)=>{const{sessionID,enabled}=value
642
- return todos.guidance(sessionID,enabled)
643
- }})
644
- notify=(sessionID,state)=>rpc.events.emit("updated",{sessionID,...state})
645
- const tools=await host.tool.transform((editor)=>{editor.namespace({name:"navigator",description:"Navigator session tasks"})
646
- editor.add({name:"todo_write",description:"Replace the Navigator Todo list for this session with the current tasks and their statuses.",input,options:{namespace:"navigator"},execute:async (value,context)=>{const state=await todos.replace(context.sessionID,value.todos)
647
- return{content:`Navigator Todo updated: ${state.todos.length} tasks.`}}})
648
- })
649
- const context=await host.session.hook("context",async (event)=>{const state=await todos.list(event.sessionID)
650
- if(state.guidance)
651
- event.system.push({type:"text",text:todoGuidance(state)})
615
+ var server_plugin_default=Plugin.define({id:"opencode-navigator.todo-server",async setup(host){const archive=await host.rpc.register(LegacyTodoArchiveRpc,{list:async (value)=>readLegacyTodoArchive((key)=>host.storage.get(key),value.sessionID)
652
616
  })
653
617
  const quotaCleanup=typeof host.integration?.connection?.active==="function"&&typeof host.provider?.get==="function"?await setupProviderQuotaServer(host):undefined
654
618
  return async ()=>{await quotaCleanup?.()
655
- await context.dispose()
656
- await tools.dispose()
657
- await rpc.dispose()
619
+ await archive.dispose()
658
620
  }}})
659
- export{todo_server_default as default
621
+ export{server_plugin_default as default
660
622
  }
@@ -10,16 +10,17 @@ _$insertNode(_el$,_$createTextNode(`Paste versioned Navigator JSON.`))
10
10
  _$effect((_$p)=>_$setProp(_el$,"fg",input.api.theme.current.textMuted,_$p))
11
11
  return _el$
12
12
  })(),onConfirm(source){const preview=input.preferences.previewPortableSettings(source)
13
- const options=[...preview.settings.layout?[{title:`Layout \xB7 ${preview.layoutChanged ? "CHANGED" : "UNCHANGED"}`,value:"status",description:`${Object.keys(preview.settings.layout.sections).length} visibility \xB7 ${Object.keys(preview.settings.layout.expanded).length} expansion values`,icon:preview.layoutChanged?"edit":"done"
13
+ const options=[...preview.settings.behavior?[{title:`Toolbar overflow \xB7 ${preview.behaviorChanged ? "CHANGED" : "UNCHANGED"}`,value:"status",description:preview.settings.behavior.toolbarOverflow??"unchanged",icon:"settings"
14
+ }]:[],...preview.settings.layout?[{title:`Layout \xB7 ${preview.layoutChanged ? "CHANGED" : "UNCHANGED"}`,value:"status",description:`${Object.keys(preview.settings.layout.sections).length} visibility \xB7 ${Object.keys(preview.settings.layout.expanded).length} expansion values`,icon:preview.layoutChanged?"edit":"done"
14
15
  }]:[],...preview.settings.mcp?[{title:`MCP \xB7 ${preview.mcpChanged ? "CHANGED" : "UNCHANGED"}`,value:"status",description:`${Object.keys(preview.settings.mcp).length} remembered server states`,icon:preview.mcpChanged?"edit":"done"
15
16
  }]:[],...preview.unsupported.map((path)=>({title:"Unsupported field \xB7 SKIPPED",value:"status",description:path,icon:"error"
16
- })),...preview.layoutChanged||preview.mcpChanged?[{title:"Apply imported settings",value:"apply",description:preview.target,icon:"done"
17
+ })),...preview.layoutChanged||preview.mcpChanged||preview.behaviorChanged?[{title:"Apply imported settings",value:"apply",description:preview.target,icon:"done"
17
18
  }]:[],{title:"Cancel",value:"cancel",description:"return without changes",icon:"close"
18
19
  }]
19
20
  let applying=false
20
21
  async function apply(){applying=true
21
22
  try{await input.preferences.applyPortableSettings(preview)
22
- input.api.ui.toast({variant:"success",title:"Portable settings",message:"Layout and MCP settings imported",duration:3000
23
+ input.api.ui.toast({variant:"success",title:"Portable settings",message:"Supported settings imported",duration:3000
23
24
  })
24
25
  input.dialogs.back()
25
26
  } catch(error){applying=false