opencode-navigator 0.22.0 → 0.23.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/tui.js CHANGED
@@ -1,9 +1,9 @@
1
- import {insert as _$insert,setProp as _$setProp,createElement as _$createElement,memo as _$memo,createComponent as _$createComponent,mergeProps as _$mergeProps,effect as _$effect,createTextNode as _$createTextNode,insertNode as _$insertNode,use as _$use,spread as _$spread,useTerminalDimensions,useTerminalDimensions as useTerminalDimensions2,useTerminalDimensions as useTerminalDimensions3,useTerminalDimensions as useTerminalDimensions4,useTerminalDimensions as useTerminalDimensions5,useTerminalDimensions as useTerminalDimensions6,useTerminalDimensions as useTerminalDimensions7,useTerminalDimensions as useTerminalDimensions8,useTerminalDimensions as useTerminalDimensions9} from "@opentui/solid"
1
+ import {insert as _$insert,setProp as _$setProp,createElement as _$createElement,memo as _$memo,createComponent as _$createComponent,mergeProps as _$mergeProps,useRenderer,effect as _$effect,createTextNode as _$createTextNode,insertNode as _$insertNode,use as _$use,spread as _$spread,useTerminalDimensions,useTerminalDimensions as useTerminalDimensions2,useTerminalDimensions as useTerminalDimensions3,useTerminalDimensions as useTerminalDimensions4,useRenderer as useRenderer2,useTerminalDimensions as useTerminalDimensions5,useTerminalDimensions as useTerminalDimensions6,useTerminalDimensions as useTerminalDimensions7,useTerminalDimensions as useTerminalDimensions8,useTerminalDimensions as useTerminalDimensions9} from "@opentui/solid"
2
2
 
3
3
 
4
4
 
5
5
 
6
- import {createSignal as createSignal2,createSignal,createEffect,onCleanup,createContext,useContext,createContext as createContext2,createSignal as createSignal3,useContext as useContext2,createEffect as createEffect2,splitProps,untrack,splitProps as splitProps2,For,createEffect as createEffect3,onCleanup as onCleanup2,createEffect as createEffect4,Show,splitProps as splitProps3,Suspense,lazy,createSignal as createSignal4,createSignal as createSignal5,onCleanup as onCleanup3,Show as Show2,batch,createSignal as createSignal7,createSignal as createSignal6,createEffect as createEffect5,createSignal as createSignal8,onCleanup as onCleanup4,createSignal as createSignal9,createSignal as createSignal10,Suspense as Suspense2,lazy as lazy2,Index,onCleanup as onCleanup5,createSignal as createSignal11,onCleanup as onCleanup6,createEffect as createEffect7,onCleanup as onCleanup8,batch as batch2,createEffect as createEffect6,createMemo,createSignal as createSignal12,onCleanup as onCleanup7,onMount,For as For2,For as For3,Show as Show3,createMemo as createMemo2,Show as Show4,createSignal as createSignal13,onCleanup as onCleanup9,For as For4,For as For5,For as For8,createMemo as createMemo4,onMount as onMount3,For as For7,createMemo as createMemo3,onMount as onMount2,Show as Show6,createEffect as createEffect8,createSignal as createSignal14,onCleanup as onCleanup10,Show as Show5,For as For6,For as For9,createEffect as createEffect9,createMemo as createMemo5,onCleanup as onCleanup11,createEffect as createEffect10,createMemo as createMemo6,untrack as untrack2,Suspense as Suspense3,lazy as lazy3,onCleanup as onCleanup12,createEffect as createEffect11,onCleanup as onCleanup13,createSignal as createSignal15,createMemo as createMemo7,createSignal as createSignal16,lazy as lazy4,Show as Show9,For as For10,Show as Show8,Show as Show7,createEffect as createEffect12,onCleanup as onCleanup14,onMount as onMount4,onCleanup as onCleanup15,onMount as onMount5,For as For11,createSignal as createSignal17,createSignal as createSignal18,Show as Show10,createSignal as createSignal20,createSignal as createSignal19,onCleanup as onCleanup16,For as For12,Show as Show13,createMemo as createMemo9,createComputed,createMemo as createMemo8,createSignal as createSignal21,untrack as untrack3,Show as Show11,createComputed as createComputed2,untrack as untrack4,Show as Show12,createSignal as createSignal23,createEffect as createEffect13,createSignal as createSignal22,on,createMemo as createMemo11,createSignal as createSignal26,createMemo as createMemo10,For as For13,Show as Show17,Show as Show14,Show as Show15,createSignal as createSignal24,createContext as createContext3,useContext as useContext3,Show as Show16,createSignal as createSignal25,onCleanup as onCleanup17,Show as Show23,createSignal as createSignal27,untrack as untrack5,createSignal as createSignal28,createSignal as createSignal29,createEffect as createEffect14,For as For15,Show as Show18,For as For14,Show as Show19,Show as Show20,Show as Show21,Show as Show22,For as For16,Show as Show25,createMemo as createMemo13,Show as Show24,createMemo as createMemo12,createSignal as createSignal30,createContext as createContext4,useContext as useContext4,Show as Show26,Suspense as Suspense4,createEffect as createEffect15,onCleanup as onCleanup18,untrack as untrack6,For as For22,Match,Switch,createMemo as createMemo19,onCleanup as onCleanup24,For as For17,Show as Show27,createMemo as createMemo15,createSignal as createSignal32,createEffect as createEffect16,createMemo as createMemo14,onCleanup as onCleanup19,untrack as untrack7,createEffect as createEffect17,createSignal as createSignal31,onCleanup as onCleanup20,For as For18,Show as Show29,createSignal as createSignal33,createEffect as createEffect18,createMemo as createMemo16,onCleanup as onCleanup21,untrack as untrack8,Show as Show28,For as For20,Show as Show32,createEffect as createEffect19,createMemo as createMemo17,createSignal as createSignal34,onCleanup as onCleanup22,untrack as untrack9,For as For19,Show as Show31,Show as Show30,Show as Show36,createEffect as createEffect20,createMemo as createMemo18,createSignal as createSignal35,onCleanup as onCleanup23,untrack as untrack10,For as For21,Show as Show34,Show as Show33,Show as Show35,createEffect as createEffect21,createMemo as createMemo20,onCleanup as onCleanup25,untrack as untrack11,Show as Show37,createMemo as createMemo21,onCleanup as onCleanup26,createEffect as createEffect22,createMemo as createMemo22,on as on2,onCleanup as onCleanup27,createSignal as createSignal36,onCleanup as onCleanup28,onMount as onMount6} from "solid-js"
6
+ import {createSignal as createSignal2,createSignal,createEffect,onCleanup,createContext,useContext,createContext as createContext2,createSignal as createSignal3,onCleanup as onCleanup2,useContext as useContext2,createEffect as createEffect2,splitProps,untrack,splitProps as splitProps2,For,createEffect as createEffect3,onCleanup as onCleanup3,createEffect as createEffect4,Show,splitProps as splitProps3,Suspense,lazy,createSignal as createSignal4,createSignal as createSignal5,onCleanup as onCleanup4,Show as Show2,batch,createSignal as createSignal7,createSignal as createSignal6,createEffect as createEffect5,createSignal as createSignal8,onCleanup as onCleanup5,createSignal as createSignal9,createSignal as createSignal10,Suspense as Suspense2,lazy as lazy2,Index,onCleanup as onCleanup6,createSignal as createSignal11,onCleanup as onCleanup7,createEffect as createEffect7,onCleanup as onCleanup9,batch as batch2,createEffect as createEffect6,createMemo,createSignal as createSignal12,onCleanup as onCleanup8,onMount,For as For2,For as For3,Show as Show3,createMemo as createMemo2,Show as Show4,createSignal as createSignal13,onCleanup as onCleanup10,For as For4,For as For5,For as For8,createMemo as createMemo4,onMount as onMount3,For as For7,createMemo as createMemo3,onMount as onMount2,Show as Show6,createEffect as createEffect8,createSignal as createSignal14,onCleanup as onCleanup11,Show as Show5,For as For6,For as For9,createEffect as createEffect9,createMemo as createMemo5,onCleanup as onCleanup12,createEffect as createEffect10,createMemo as createMemo6,untrack as untrack2,Suspense as Suspense3,lazy as lazy3,onCleanup as onCleanup13,createEffect as createEffect11,onCleanup as onCleanup14,createSignal as createSignal15,createMemo as createMemo7,createSignal as createSignal16,lazy as lazy4,Show as Show9,For as For10,Show as Show8,Show as Show7,createEffect as createEffect12,onCleanup as onCleanup15,onMount as onMount4,onCleanup as onCleanup16,onMount as onMount5,For as For11,createSignal as createSignal17,createSignal as createSignal18,Show as Show10,createSignal as createSignal20,createSignal as createSignal19,onCleanup as onCleanup17,For as For12,Show as Show13,createMemo as createMemo9,createComputed,createMemo as createMemo8,createSignal as createSignal21,untrack as untrack3,Show as Show11,createComputed as createComputed2,untrack as untrack4,Show as Show12,createSignal as createSignal23,createEffect as createEffect13,createSignal as createSignal22,on,createMemo as createMemo11,createSignal as createSignal26,createMemo as createMemo10,For as For13,Show as Show17,Show as Show14,Show as Show15,createSignal as createSignal24,createContext as createContext3,useContext as useContext3,Show as Show16,createSignal as createSignal25,onCleanup as onCleanup18,Show as Show23,createSignal as createSignal27,untrack as untrack5,createSignal as createSignal28,createSignal as createSignal29,createEffect as createEffect14,For as For15,Show as Show18,For as For14,createSignal as createSignal30,Show as Show19,Show as Show20,Show as Show21,Show as Show22,For as For16,Show as Show25,createMemo as createMemo13,Show as Show24,createMemo as createMemo12,createSignal as createSignal31,createContext as createContext4,useContext as useContext4,Show as Show26,Suspense as Suspense4,createEffect as createEffect15,onCleanup as onCleanup19,untrack as untrack6,For as For22,Match,Switch,createMemo as createMemo19,onCleanup as onCleanup25,For as For17,Show as Show27,createMemo as createMemo15,createSignal as createSignal33,createEffect as createEffect16,createMemo as createMemo14,onCleanup as onCleanup20,untrack as untrack7,createEffect as createEffect17,createSignal as createSignal32,onCleanup as onCleanup21,For as For18,Show as Show29,createSignal as createSignal34,createEffect as createEffect18,createMemo as createMemo16,onCleanup as onCleanup22,untrack as untrack8,Show as Show28,For as For20,Show as Show32,createEffect as createEffect19,createMemo as createMemo17,createSignal as createSignal35,onCleanup as onCleanup23,untrack as untrack9,For as For19,Show as Show31,Show as Show30,Show as Show36,createEffect as createEffect20,createMemo as createMemo18,createSignal as createSignal36,onCleanup as onCleanup24,untrack as untrack10,For as For21,Show as Show34,Show as Show33,Show as Show35,createEffect as createEffect21,createMemo as createMemo20,onCleanup as onCleanup26,untrack as untrack11,Show as Show37,createMemo as createMemo21,onCleanup as onCleanup27,createEffect as createEffect22,createMemo as createMemo22,on as on2,onCleanup as onCleanup28,createRoot,createSignal as createSignal37,getOwner,createSignal as createSignal38,onCleanup as onCleanup29,onMount as onMount6} from "solid-js"
7
7
  var SIDEBAR_SECTIONS=["limits","todo","subagents","skills","quick_actions","lsp","mcp"]
8
8
  var QUICK_ACTION_IDS=["session.rename","session.timeline","session.copy","session.export","session.compact","session.list","session.new","session.fork","messages.copy","session.first","session.last","session.messages_last_user","session.message.next","session.message.previous","permission.mode"]
9
9
  function preferencesScope(path){return path.worktree||path.directory||"global"
@@ -230,7 +230,7 @@ return
230
230
  const error=status.error
231
231
  return typeof error==="string"?error:undefined
232
232
  }
233
- function createMcpController(api,persist,preferences){const [snapshots,setSnapshots]=createSignal2({})
233
+ function createMcpController(api,persist,preferences,source){const [snapshots,setSnapshots]=createSignal2({})
234
234
  const [mutationCounts,setMutationCounts]=createSignal2({})
235
235
  const [bulkStates,setBulkStates]=createSignal2({})
236
236
  const [selectedPresets,setSelectedPresets]=createSignal2({})
@@ -303,7 +303,7 @@ const failed=state.failed.filter((candidate)=>candidate!==name)
303
303
  bulkRetries.set(current.key,(bulkRetries.get(current.key)??[]).filter((candidate)=>candidate.name!==name))
304
304
  return{...values,[current.key]:{...state,status:failed.length>0?"error":"ready",failed}}})
305
305
  }
306
- function list(current=target()){return snapshots()[current.key]??api.state.mcp()
306
+ function list(current=target()){return source?source.list(current):snapshots()[current.key]??api.state.mcp()
307
307
  }
308
308
  async function refresh(current=target(),force=false){const pending=refreshing.get(current.key)
309
309
  if(pending&&!force)
@@ -311,8 +311,7 @@ return pending
311
311
  const requestState=requests.start(current.key,"refresh MCP servers",current.key in snapshots(),force)
312
312
  if(!requestState)
313
313
  return pending
314
- const request=api.client.mcp.status(current.routing,{throwOnError:true,signal:requestState.signal}).then((result)=>{const items=Object.entries(result.data??{}).map(([name,status])=>({name,status:status.status,error:statusError(status)})).sort((a,b)=>a.name.localeCompare(b.name))
315
- if(requestState.isCurrent())
314
+ const request=(source?source.refresh(current,force,requestState.signal):api.client.mcp.status(current.routing,{throwOnError:true,signal:requestState.signal}).then((result)=>Object.entries(result.data??{}).map(([name,status])=>({name,status:status.status,error:statusError(status)})).sort((a,b)=>a.name.localeCompare(b.name)))).then((items)=>{if(!source&&requestState.isCurrent())
316
315
  setSnapshots((value)=>({...value,[current.key]:items}))
317
316
  if(requestState.isCurrent())
318
317
  reconcileSelectedPreset(current,items)
@@ -684,6 +683,7 @@ return _$createComponent(DialogContext.Provider,{get value(){return props.scope
684
683
  }})
685
684
  }
686
685
 
686
+
687
687
  function useDialogs(api){const scope=useContext2(DialogContext)
688
688
  const icons=useIcons()
689
689
  if(scope?.api===api)
@@ -700,17 +700,31 @@ const state=createSignal3(initial)
700
700
  scope.frame.state.set(key,state)
701
701
  return state
702
702
  }
703
- function useDialogScroll(key="scroll"){const [position,setPosition]=useDialogState(key,0)
703
+ function useDialogScroll(key="scroll"){const renderer=useRenderer()
704
+ const [position,setPosition]=useDialogState(key,0)
704
705
  let body
705
706
  let isPending=true
706
- return{ref(node){body=node
707
- },restore(){if(!isPending||!body)
707
+ function restore(){if(!isPending||!body||body.isDestroyed)
708
+ return
709
+ if(position()>0&&body.scrollHeight===0)
708
710
  return
709
711
  isPending=false
710
712
  body.scrollTo(position())
711
- },save(){if(body&&!body.isDestroyed)
713
+ }
714
+ function save(){if(!isPending&&body&&!body.isDestroyed)
712
715
  setPosition(body.scrollTop)
713
- }}}
716
+ }
717
+ const frame=()=>{restore()
718
+ save()
719
+ }
720
+ renderer.on("frame",frame)
721
+ onCleanup2(()=>{save()
722
+ renderer.off("frame",frame)
723
+ })
724
+ return{ref(node){body=node
725
+ renderer.requestRender()
726
+ },restore,save
727
+ }}
714
728
 
715
729
 
716
730
 
@@ -951,7 +965,7 @@ const unregister=props.api.keymap.registerLayer({mode:"modal",priority:1000,comm
951
965
  },{key:"space",cmd:`${prefix}.select`
952
966
  }]
953
967
  })
954
- onCleanup2(unregister)
968
+ onCleanup3(unregister)
955
969
  return _$createComponent(DialogSurface,{get api(){return props.api
956
970
  },get lift(){return props.lift
957
971
  },get children(){return [(()=>{var _el$=_$createElement("box"),_el$2=_$createElement("text"),_el$3=_$createTextNode(` `),_el$4=_$createElement("text")
@@ -991,9 +1005,9 @@ _$insert(_el$1,(()=>{var _c$=_$memo(()=>!!option.icon)
991
1005
  return()=>_c$()?`${icons.icon(option.icon)} `:""
992
1006
  })(),null)
993
1007
  _$insert(_el$1,()=>option.title,null)
994
- _$effect((_p$)=>{var _v$8=active()===index()?TextAttributes2.BOLD:undefined,_v$9=theme().text
995
- _v$8!==_p$.e&&(_p$.e=_$setProp(_el$1,"attributes",_v$8,_p$.e))
996
- _v$9!==_p$.t&&(_p$.t=_$setProp(_el$1,"fg",_v$9,_p$.t))
1008
+ _$effect((_p$)=>{var _v$5=active()===index()?TextAttributes2.BOLD:undefined,_v$6=theme().text
1009
+ _v$5!==_p$.e&&(_p$.e=_$setProp(_el$1,"attributes",_v$5,_p$.e))
1010
+ _v$6!==_p$.t&&(_p$.t=_$setProp(_el$1,"fg",_v$6,_p$.t))
997
1011
  return _p$
998
1012
  },{e:undefined,t:undefined
999
1013
  })
@@ -1006,13 +1020,7 @@ return _el$10
1006
1020
  })()]
1007
1021
  }})
1008
1022
  }))
1009
- _$effect((_p$)=>{var{restore:_v$5,save:_v$6}=scroll,_v$7=props.adaptive?Math.min(props.height??Math.min(props.options.length*3,15),Math.max(2,Math.floor(dimensions().height*0.75)-8)):props.height??Math.min(props.options.length*3,15)
1010
- _v$5!==_p$.e&&(_p$.e=_$setProp(_el$5,"renderBefore",_v$5,_p$.e))
1011
- _v$6!==_p$.t&&(_p$.t=_$setProp(_el$5,"renderAfter",_v$6,_p$.t))
1012
- _v$7!==_p$.a&&(_p$.a=_$setProp(_el$5,"height",_v$7,_p$.a))
1013
- return _p$
1014
- },{e:undefined,t:undefined,a:undefined
1015
- })
1023
+ _$effect((_$p)=>_$setProp(_el$5,"height",props.adaptive?Math.min(props.height??Math.min(props.options.length*3,15),Math.max(2,Math.floor(dimensions().height*0.75)-8)):props.height??Math.min(props.options.length*3,15),_$p))
1016
1024
  return _el$5
1017
1025
  })(),(()=>{var _el$7=_$createElement("text"),_el$8=_$createTextNode(` navigate \xB7 `),_el$9=_$createTextNode(` select \xB7 `),_el$0=_$createTextNode(` close`)
1018
1026
  _$insertNode(_el$7,_el$8)
@@ -2283,7 +2291,7 @@ return{behavior,layout:{sections,expanded:{},...sectionOrder&&{order:sectionOrde
2283
2291
  function configuredDefaultsFromPluginOptions(options){return mergeConfiguredDefaults(legacyDefaults(pluginConfig(options)),parseConfiguredDefaults(options??{}))
2284
2292
  }
2285
2293
 
2286
- function createSkillController(api){const [skills,setSkills]=createSignal5({})
2294
+ function createSkillController(api,source){const [skills,setSkills]=createSignal5({})
2287
2295
  const refreshing=new Map
2288
2296
  const targets=new Map
2289
2297
  const requests=createRequestState(api.lifecycle.signal)
@@ -2292,7 +2300,7 @@ let activeTarget
2292
2300
  let leases=new Set
2293
2301
  function target(){return currentLocation(api)
2294
2302
  }
2295
- function list(current=target()){return skills()[current.key]??[]
2303
+ function list(current=target()){return source?source.list(current):skills()[current.key]??[]
2296
2304
  }
2297
2305
  function error(current=target()){return requests.state(current.key).error?.message
2298
2306
  }
@@ -2303,8 +2311,8 @@ targets.set(current.key,current)
2303
2311
  const requestState=requests.start(current.key,"refresh skills",current.key in skills(),force)
2304
2312
  if(!requestState)
2305
2313
  return pending
2306
- const request=api.client.app.skills(current.routing,{throwOnError:true,signal:requestState.signal}).then((result)=>{const items=[...result.data??[]].sort((a,b)=>a.name.localeCompare(b.name))
2307
- if(requestState.isCurrent())
2314
+ const request=(source?source.refresh(current,force,requestState.signal).then((data)=>({data})):api.client.app.skills(current.routing,{throwOnError:true,signal:requestState.signal})).then((result)=>{const items=[...result.data??[]].sort((a,b)=>a.name.localeCompare(b.name))
2315
+ if(!source&&requestState.isCurrent())
2308
2316
  setSkills((value)=>({...value,[current.key]:items}))
2309
2317
  requestState.succeed()
2310
2318
  return items
@@ -2340,12 +2348,12 @@ targets.delete(current.key)
2340
2348
  requests.abortAll()
2341
2349
  promptRequests.abortAll()
2342
2350
  refreshing.clear()
2343
- }},async use(current,name){if(activeTarget&&activeTarget!==current.key||target().key!==current.key)
2351
+ }},async use(current,name,selected){if(activeTarget&&activeTarget!==current.key||target().key!==current.key)
2344
2352
  throw new DOMException("Sidebar context changed","AbortError")
2345
2353
  const request=promptRequests.start(current.key,"insert skill",false,true)
2346
2354
  if(!request)
2347
2355
  return false
2348
- try{const result=await api.client.tui.appendPrompt({...current.routing,text:`/${name} `},{throwOnError:true,signal:request.signal})
2356
+ try{const result=source&&selected?{data:await source.use(current,selected,request.signal)}:await api.client.tui.appendPrompt({...current.routing,text:`/${name} `},{throwOnError:true,signal:request.signal})
2349
2357
  if(!request.isCurrent())
2350
2358
  return false
2351
2359
  request.succeed()
@@ -2578,7 +2586,7 @@ const unregister=props.api.keymap.registerLayer({mode:"modal",priority:1000,comm
2578
2586
  },{key:"return",cmd:`${PLUGIN_ID}.skill-dialog.submit`
2579
2587
  }]
2580
2588
  })
2581
- onCleanup3(unregister)
2589
+ onCleanup4(unregister)
2582
2590
  return _$createComponent(DialogSurface,{get api(){return props.api
2583
2591
  },get children(){return [_$createComponent(SkillDialogHeader,{get api(){return props.api
2584
2592
  },get name(){return props.skill.name
@@ -2601,11 +2609,15 @@ return _$createComponent(DialogSurface,{get api(){return props.api
2601
2609
  function skillUnavailable(controller,skill){const list=controller.list()
2602
2610
  if(list.every((item)=>!(item.name===skill.name&&item.location===skill.location)))
2603
2611
  return "Skill is unavailable in this workspace"
2604
- if(list.filter((item)=>item.name===skill.name).length!==1)
2612
+ if(skill.id){if(list.every((item)=>item.id!==skill.id||item.location!==skill.location))
2613
+ return "Skill source changed"
2614
+ } else if(list.filter((item)=>item.name===skill.name).length!==1)
2605
2615
  return "Multiple sources share this skill name"
2606
2616
  return
2607
2617
  }
2608
- function activateSkill(input){const target=input.controller.target()
2618
+ function activateSkill(input){input={...input,skill:{...input.skill
2619
+ }}
2620
+ const target=input.controller.target()
2609
2621
  const route=JSON.stringify(input.api.route.current)
2610
2622
  const current=()=>target.key===input.controller.target().key&&route===JSON.stringify(input.api.route.current)
2611
2623
  const report=(error)=>{if(!isAbortError(error))
@@ -2622,7 +2634,7 @@ async function insert(skip=false){try{check()
2622
2634
  if(skip)
2623
2635
  input.access.skipSkillConfirmation(input.skill)
2624
2636
  input.beforeInsert?.()
2625
- if(await input.controller.use(target,input.skill.name))
2637
+ if(await input.controller.use(target,input.skill.name,input.skill))
2626
2638
  await input.access.recordSkillUse?.(input.skill)
2627
2639
  } catch(error){report(error)
2628
2640
  }}
@@ -2699,7 +2711,8 @@ return targets.get(key)?.recent??[]
2699
2711
  var POLL_INTERVAL_MS=5000
2700
2712
  function activeStatus(status){return status?.type==="busy"||status?.type==="retry"
2701
2713
  }
2702
- function createSubagentController(api,options={}){const now=options.now??Date.now
2714
+ function createSubagentController(api,options={}){const source=options.source
2715
+ const now=options.now??Date.now
2703
2716
  const history=createSubagentHistory({now})
2704
2717
  const [children,setChildren]=createSignal7({})
2705
2718
  const [statuses,setStatuses]=createSignal7({})
@@ -2712,6 +2725,7 @@ const remoteRetryAt=new Map
2712
2725
  const requests=createRequestState(api.lifecycle.signal)
2713
2726
  let activeTarget
2714
2727
  let pollTimer
2728
+ const snapshots=()=>source?Object.fromEntries([...targets].map(([key,current])=>[key,source.list(current.parentID)])):children()
2715
2729
  function target(parentID){const parent=api.state.session.get(parentID)
2716
2730
  const directory=parent?.directory??api.state.path.directory
2717
2731
  const workspace=parent?.workspaceID
@@ -2730,16 +2744,18 @@ history.remove(key,sessionID)
2730
2744
  }
2731
2745
  for(const journal of journals)
2732
2746
  journal.sessions.push(mutation)
2747
+ if(!source)
2733
2748
  setChildren((current)=>Object.fromEntries(Object.entries(current).map(([key,items])=>[key,applySession(items,targets.get(key).parentID,mutation)])))
2734
2749
  }
2735
2750
  function recordStatus(input){const mutation={...input,at:now(),observed:new Set}
2736
2751
  for(const journal of journals)
2737
2752
  journal.statuses.push(mutation)
2738
- batch(()=>{for(const [key,items] of Object.entries(children())){if(items.every((item)=>item.id!==mutation.sessionID))
2753
+ batch(()=>{for(const [key,items] of Object.entries(snapshots())){if(items.every((item)=>item.id!==mutation.sessionID))
2739
2754
  continue
2740
2755
  history.observeStatus(key,mutation.sessionID,mutation.status,mutation.at)
2741
2756
  mutation.observed.add(key)
2742
2757
  }
2758
+ if(!source)
2743
2759
  setStatuses((current)=>Object.fromEntries(Object.entries(current).map(([key,value])=>[key,applyStatus(value,mutation)])))
2744
2760
  })
2745
2761
  }
@@ -2748,7 +2764,7 @@ return
2748
2764
  const mutation={sessionID:input.sessionID,error:input.error,at:now(),observed:new Set}
2749
2765
  for(const journal of journals)
2750
2766
  journal.errors.push(mutation)
2751
- for(const [key,items] of Object.entries(children())){if(items.every((item)=>item.id!==mutation.sessionID))
2767
+ for(const [key,items] of Object.entries(snapshots())){if(items.every((item)=>item.id!==mutation.sessionID))
2752
2768
  continue
2753
2769
  history.observeError(key,mutation.sessionID,mutation.error,mutation.at)
2754
2770
  mutation.observed.add(key)
@@ -2765,12 +2781,13 @@ if(!requestState){journals.delete(journal)
2765
2781
  return pending
2766
2782
  }
2767
2783
  const signal=AbortSignal.any([requestState.signal,AbortSignal.timeout(5000)])
2768
- const request=Promise.all([api.client.session.children({sessionID:current.parentID,...current.routing},{throwOnError:true,signal}),api.client.session.status(current.routing,{throwOnError:true,signal})]).then(async ([childResult,statusResult])=>{let nextChildren=childResult.data??[]
2784
+ const request=(source?source.refresh(current.parentID,signal).then(()=>[{data:source.list(current.parentID)},{data:Object.fromEntries(source.list(current.parentID).map((item)=>[item.id,source.status(item.id)]))
2785
+ }]):Promise.all([api.client.session.children({sessionID:current.parentID,...current.routing},{throwOnError:true,signal}),api.client.session.status(current.routing,{throwOnError:true,signal})])).then(async ([childResult,statusResult])=>{let nextChildren=childResult.data??[]
2769
2786
  for(const mutation of journal.sessions)
2770
2787
  nextChildren=applySession(nextChildren,current.parentID,mutation)
2771
2788
  let nextStatuses={...statusResult.data}
2772
2789
  const missing={}
2773
- const remoteStatuses=await Promise.all(nextChildren.map((session)=>{const key=JSON.stringify([current.key,session.id])
2790
+ const remoteStatuses=source?[]:await Promise.all(nextChildren.map((session)=>{const key=JSON.stringify([current.key,session.id])
2774
2791
  return(remoteRetryAt.get(key)??0)<=now()?fetchDevTeamStatus(session,requestState.signal):{sessionID:session.id,failed:true,cooldown:true,status:undefined}}))
2775
2792
  if(!requestState.isCurrent())
2776
2793
  return
@@ -2807,8 +2824,9 @@ history.observeStatus(current.key,session.id,status)
2807
2824
  for(const mutation of journal.errors){if(mutation.sessionID===session.id&&!mutation.observed.has(current.key))
2808
2825
  history.observeError(current.key,session.id,mutation.error,mutation.at)
2809
2826
  }}
2810
- setChildren((value)=>({...value,[current.key]:nextChildren}))
2827
+ if(!source){setChildren((value)=>({...value,[current.key]:nextChildren}))
2811
2828
  setStatuses((value)=>({...value,[current.key]:nextStatuses}))
2829
+ }
2812
2830
  setUnavailable((value)=>({...value,[current.key]:missing}))
2813
2831
  })
2814
2832
  }
@@ -2826,13 +2844,13 @@ return request
2826
2844
  function refresh(parentID,force=false){return refreshTarget(target(parentID),force)
2827
2845
  }
2828
2846
  function list(parentID){const current=target(parentID)
2829
- const currentStatuses=statuses()[current.key]??{}
2830
- return(children()[current.key]??[]).map((session)=>({session,status:currentStatuses[session.id]??(unavailable()[current.key]?.[session.id]?undefined:api.state.session.status(session.id))
2847
+ const currentStatuses=source?Object.fromEntries(source.list(parentID).map((item)=>[item.id,source.status(item.id)])):statuses()[current.key]??{}
2848
+ return(snapshots()[current.key]??[]).map((session)=>({session,status:currentStatuses[session.id]??(unavailable()[current.key]?.[session.id]?undefined:api.state.session.status(session.id))
2831
2849
  })).filter((item)=>activeStatus(item.status)).map((item)=>({...item,run:history.active(current.key,item.session.id),unavailable:unavailable()[current.key]?.[item.session.id]
2832
2850
  }))
2833
2851
  }
2834
2852
  function recent(parentID){const current=target(parentID)
2835
- const sessions=new Map((children()[current.key]??[]).map((session)=>[session.id,session]))
2853
+ const sessions=new Map((snapshots()[current.key]??[]).map((session)=>[session.id,session]))
2836
2854
  return history.recent(current.key).flatMap((run)=>{const session=sessions.get(run.sessionID)
2837
2855
  return session?[{session,status:{type:"idle"},run,unavailable:false}]:[]
2838
2856
  })
@@ -2862,6 +2880,7 @@ return
2862
2880
  refreshTarget(current).catch(()=>{})
2863
2881
  pollTimer=setTimeout(poll,POLL_INTERVAL_MS)
2864
2882
  }
2883
+ if(!source)
2865
2884
  pollTimer=setTimeout(poll,POLL_INTERVAL_MS)
2866
2885
  }
2867
2886
  return()=>{if(activeTarget!==current.key)
@@ -2929,7 +2948,7 @@ createEffect5(()=>{if(key()===undefined)
2929
2948
  return
2930
2949
  setNow(Date.now())
2931
2950
  const timer=setInterval(()=>setNow(Date.now()),1000)
2932
- onCleanup4(()=>clearInterval(timer))
2951
+ onCleanup5(()=>clearInterval(timer))
2933
2952
  })
2934
2953
  return now
2935
2954
  }
@@ -3026,6 +3045,21 @@ function openKeyboardHelp(api,iconStyle=()=>"nerd",multilineCorners=()=>true,sho
3026
3045
 
3027
3046
 
3028
3047
 
3048
+ var defaults={todo:true,lsp:true}
3049
+ var capabilities=new WeakMap
3050
+ function setHostCapabilities(api,value){capabilities.set(api,value)
3051
+ }
3052
+ function hostCapabilities(api){return capabilities.get(api)??defaults
3053
+ }
3054
+ function hostCapabilityUnavailable(api,section){return section==="todo"||section==="lsp"?hostCapabilities(api).unavailable?.[section]:undefined
3055
+ }
3056
+ function supportsSidebarSection(api,section){const current=hostCapabilities(api)
3057
+ return section!=="todo"&&section!=="lsp"?true:current[section]
3058
+ }
3059
+ function supportsTitleVisibility(api){return hostCapabilities(api).titleVisibility!==false
3060
+ }
3061
+ function supportsPermissionMode(api){return hostCapabilities(api).permissionMode!==false
3062
+ }
3029
3063
  var QUICK_ACTION_METADATA=[["\uEA73","Rename"],["\uEA82","Timeline"],["\uEBCC","Copy transcript"],["\uEBAC","Export"],["\uEAF5","Compact"],["\uEBEB","Switch session"],["\uEA60","New session"],["\uEB9E","Fork session"],["\uEBCC","Copy last response"],["\uEAA1","First message"],["\uEA9A","Last message"],["\uEA82","Last user message"],["\uEA9A","Next message"],["\uEAA1","Previous message"],["\uEBB3","Auto-approve permissions"]]
3030
3064
  var QUICK_ACTIONS=QUICK_ACTION_IDS.map((command,index)=>({command,icon:QUICK_ACTION_METADATA[index][0],label:QUICK_ACTION_METADATA[index][1]
3031
3065
  }))
@@ -3039,7 +3073,7 @@ return typeof command?.title==="string"?command.title:action.label
3039
3073
  function createQuickActionRevision(api){const [revision,setRevision]=createSignal11(0)
3040
3074
  const unsubscribe=api.keymap.on?.("state",()=>setRevision((value)=>value + 1))
3041
3075
  if(unsubscribe)
3042
- onCleanup6(unsubscribe)
3076
+ onCleanup7(unsubscribe)
3043
3077
  return revision
3044
3078
  }
3045
3079
  function orderQuickActionIds(order,favorites){return [...order.filter((id)=>favorites.has(id)),...order.filter((id)=>!favorites.has(id))]
@@ -3052,7 +3086,7 @@ if(!getCommands)
3052
3086
  return
3053
3087
  const command=getCommands({visibility:"registered"}).find((candidate)=>candidate.name===action.command)
3054
3088
  if(!command)
3055
- return "Unavailable in this OpenCode version"
3089
+ return hostCapabilities(api).commandsReachableOnly?"Unavailable in the current session":"Unavailable in this OpenCode version"
3056
3090
  const enabled=command.enabled
3057
3091
  if(enabled===false||typeof enabled==="function"&&!enabled())
3058
3092
  return "Unavailable in the current session"
@@ -3165,11 +3199,24 @@ const contrast=background.r*0.299 + background.g*0.587 + background.b*0.114>0.5?
3165
3199
  return{title:mixColor(parseColor2(theme().text),RGBA3.fromValues(contrast,contrast,contrast),0.45),description:mixColor(parseColor2(theme().textMuted),background,0.4)
3166
3200
  }})
3167
3201
  const dimensions=useTerminalDimensions5()
3202
+ const renderer=useRenderer2()
3168
3203
  let input
3169
3204
  let body
3170
3205
  let pendingScroll=scrollTop()
3171
3206
  let isDisposed=false
3172
3207
  let refreshGeneration=0
3208
+ const onFrame=()=>{if(!body||body.isDestroyed)
3209
+ return
3210
+ const position=pendingScroll
3211
+ pendingScroll=undefined
3212
+ if(typeof position==="number")
3213
+ body.scrollTo(position)
3214
+ else if(typeof position==="string")
3215
+ body.scrollChildIntoView(`${prefix}.${position}`)
3216
+ setScrollTop(body.scrollTop)
3217
+ }
3218
+ renderer.on("frame",onFrame)
3219
+ onCleanup8(()=>renderer.off("frame",onFrame))
3173
3220
  const sameContext=()=>searchContext(props.api).key===context.key
3174
3221
  const candidates=createMemo(()=>{commandRevision()
3175
3222
  return buildSearchCandidates({skills:props.skills.list(skillTarget),subagents:context.sessionID?buildSubagentView(props.subagents.list(context.sessionID),props.subagents.recent(context.sessionID)):[],mcp:props.mcp.list(mcpTarget),actionOrder:props.preferences.quickActionOrder?.()??QUICK_ACTION_IDS,favoriteQuickActions:props.preferences.favoriteQuickActions?.()??new Set,favoriteSkills:props.preferences.favoriteSkills(),skillGroups:props.preferences.skillGroups?.()??{},recentSkills:props.preferences.recentSkills(),favoriteMcp:props.preferences.favoriteMcpServers(),mcpGroups:props.preferences.mcpServerGroups?.()??{},hasSession:Boolean(context.sessionID),mcpBusy:props.mcp.mutating(mcpTarget)||props.mcp.bulkState(mcpTarget).status==="running",actionDisabledReason:(action)=>quickActionDisabledReason(props.api,action),actionLabel:(action)=>quickActionLabel(props.api,action)
@@ -3275,10 +3322,10 @@ const unregister=props.api.keymap.registerLayer({mode:"modal",priority:1000,comm
3275
3322
  },{key:"ctrl+r",cmd:`${prefix}.retry`
3276
3323
  }]
3277
3324
  })
3278
- onCleanup7(()=>{isDisposed=true
3325
+ onCleanup8(()=>{isDisposed=true
3279
3326
  unregister()
3280
3327
  })
3281
- onCleanup7(props.skills.activate?.(skillTarget)??(()=>{}))
3328
+ onCleanup8(props.skills.activate?.(skillTarget)??(()=>{}))
3282
3329
  onMount(()=>{input?.focus()
3283
3330
  refresh().catch(report)
3284
3331
  })
@@ -3291,17 +3338,7 @@ return{active,activeTab,activate,allErrors,byID,dialogs,errors,icons,loading,mat
3291
3338
  pendingScroll=0
3292
3339
  setQueryValue(value)
3293
3340
  setSelectedID(undefined)
3294
- },setSelectedID,renderBefore(){const position=pendingScroll
3295
- pendingScroll=undefined
3296
- if(!body||position===undefined)
3297
- return
3298
- if(typeof position==="number")
3299
- body.scrollTo(position)
3300
- else
3301
- body.scrollChildIntoView(`${prefix}.${position}`)
3302
- },renderAfter(){if(body)
3303
- setScrollTop(body.scrollTop)
3304
- },resultHeight:()=>Math.min(Math.max(3,Math.min(16,Math.floor(dimensions().height*0.5)-9)),Math.max(1,results().length*3 + errors().length*2)),theme
3341
+ },setSelectedID,resultHeight:()=>Math.min(Math.max(3,Math.min(16,Math.floor(dimensions().height*0.5)-9)),Math.max(1,results().length*3 + errors().length*2)),theme
3305
3342
  }}
3306
3343
 
3307
3344
 
@@ -3470,13 +3507,11 @@ return _el$8
3470
3507
  })
3471
3508
  })
3472
3509
  }}),null)
3473
- _$effect((_p$)=>{var _v$=`${model.prefix}.results`,{renderBefore:_v$2,renderAfter:_v$3}=model,_v$4=model.resultHeight()
3510
+ _$effect((_p$)=>{var _v$=`${model.prefix}.results`,_v$2=model.resultHeight()
3474
3511
  _v$!==_p$.e&&(_p$.e=_$setProp(_el$,"id",_v$,_p$.e))
3475
- _v$2!==_p$.t&&(_p$.t=_$setProp(_el$,"renderBefore",_v$2,_p$.t))
3476
- _v$3!==_p$.a&&(_p$.a=_$setProp(_el$,"renderAfter",_v$3,_p$.a))
3477
- _v$4!==_p$.o&&(_p$.o=_$setProp(_el$,"height",_v$4,_p$.o))
3512
+ _v$2!==_p$.t&&(_p$.t=_$setProp(_el$,"height",_v$2,_p$.t))
3478
3513
  return _p$
3479
- },{e:undefined,t:undefined,a:undefined,o:undefined
3514
+ },{e:undefined,t:undefined
3480
3515
  })
3481
3516
  return _el$
3482
3517
  })()
@@ -3523,9 +3558,9 @@ createEffect7(()=>{const key=props.preferences.searchKey()
3523
3558
  const remove=props.api.keymap.registerLayer({mode:"base",bindings:[{key,cmd:SEARCH_COMMAND
3524
3559
  }]
3525
3560
  })
3526
- onCleanup8(remove)
3561
+ onCleanup9(remove)
3527
3562
  })
3528
- onCleanup8(()=>{isDisposed=true
3563
+ onCleanup9(()=>{isDisposed=true
3529
3564
  unregister()
3530
3565
  for(const timer of timers)
3531
3566
  clearTimeout(timer)
@@ -3562,21 +3597,6 @@ return value&&valid()&&!blocked()?controller.applyPreset(name,value,target):unde
3562
3597
 
3563
3598
 
3564
3599
 
3565
- var defaults={todo:true,lsp:true}
3566
- var capabilities=new WeakMap
3567
- function setHostCapabilities(api,value){capabilities.set(api,value)
3568
- }
3569
- function hostCapabilities(api){return capabilities.get(api)??defaults
3570
- }
3571
- function hostCapabilityUnavailable(api,section){return section==="todo"||section==="lsp"?hostCapabilities(api).unavailable?.[section]:undefined
3572
- }
3573
- function supportsSidebarSection(api,section){const current=hostCapabilities(api)
3574
- return section!=="todo"&&section!=="lsp"?true:current[section]
3575
- }
3576
- function supportsTitleVisibility(api){return hostCapabilities(api).titleVisibility!==false
3577
- }
3578
- function supportsPermissionMode(api){return hostCapabilities(api).permissionMode!==false
3579
- }
3580
3600
  var ONBOARDING_STEPS=[{title:"A control center that stays compact",summary:"Navigator brings session state, search, and controls into OpenCode's existing sidebar.",icon:"help",features:[{icon:"sections",title:"Progressive disclosure",description:"Expand only the context you need right now."},{icon:"scope",title:"Current context",description:"Follow the active session, workspace, and worktree."},{icon:"actions",title:"Mouse and keyboard",description:"Use the same controls with either input method."}]
3581
3601
  },{title:"Stay oriented while agents work",summary:"Keep current work visible without turning the sidebar into another dashboard.",icon:"todo",features:[{icon:"todo",title:"Todo filters",description:"Separate active, finished, and cancelled work."},{icon:"subagents",title:"Live subagents",description:"See running, retrying, recent, and failed child sessions; open them directly."
3582
3602
  },{icon:"info",title:"Provider limits",description:"Link a matching Codex account for model quotas and confirmed banked resets."
@@ -3798,7 +3818,7 @@ const unregister=props.api.keymap.registerLayer({mode:"modal",priority:1000,comm
3798
3818
  },{key:"return",cmd:`${PLUGIN_ID}.wizard.select`
3799
3819
  }]
3800
3820
  })
3801
- onCleanup9(unregister)
3821
+ onCleanup10(unregister)
3802
3822
  return _$createComponent(DialogSurface,{get api(){return props.api
3803
3823
  },get children(){return [(()=>{var _el$=_$createElement("box"),_el$2=_$createElement("text"),_el$3=_$createTextNode(` Navigator setup`),_el$4=_$createElement("text"),_el$5=_$createTextNode(`Step `),_el$6=_$createTextNode(`/`),_el$7=_$createTextNode(` \xB7 `)
3804
3824
  _$insertNode(_el$,_el$2)
@@ -3974,7 +3994,7 @@ const unregister=props.api.keymap.registerLayer({mode:"modal",priority:1000,comm
3974
3994
  },{key:"ctrl+r",cmd:`${prefix}.refresh`
3975
3995
  }]
3976
3996
  })
3977
- onCleanup10(unregister)
3997
+ onCleanup11(unregister)
3978
3998
  createEffect8(()=>{if(!props.valid())
3979
3999
  dialogs.close()
3980
4000
  })
@@ -4014,13 +4034,7 @@ _$setProp(_el$5,"contentOptions",{minHeight:0
4014
4034
  })
4015
4035
  _$setProp(_el$6,"gap",1)
4016
4036
  _$insert(_el$6,()=>props.children)
4017
- _$effect((_p$)=>{var{restore:_v$3,save:_v$4}=scroll,_v$5=Math.min(Math.max(2,Math.floor(dimensions().height*0.75)-14),Math.max(1,props.rowCount*3-1))
4018
- _v$3!==_p$.e&&(_p$.e=_$setProp(_el$5,"renderBefore",_v$3,_p$.e))
4019
- _v$4!==_p$.t&&(_p$.t=_$setProp(_el$5,"renderAfter",_v$4,_p$.t))
4020
- _v$5!==_p$.a&&(_p$.a=_$setProp(_el$5,"height",_v$5,_p$.a))
4021
- return _p$
4022
- },{e:undefined,t:undefined,a:undefined
4023
- })
4037
+ _$effect((_$p)=>_$setProp(_el$5,"height",Math.min(Math.max(2,Math.floor(dimensions().height*0.75)-14),Math.max(1,props.rowCount*3-1)),_$p))
4024
4038
  return _el$5
4025
4039
  })(),_$createComponent(Show6,{get when(){return props.blocked||props.notice
4026
4040
  },get children(){var _el$7=_$createElement("box"),_el$8=_$createElement("text"),_el$9=_$createElement("text")
@@ -4034,9 +4048,9 @@ _$setProp(_el$9,"flexGrow",1)
4034
4048
  _$setProp(_el$9,"wrapMode","word")
4035
4049
  _$setProp(_el$9,"height",2)
4036
4050
  _$insert(_el$9,()=>props.blocked||props.notice)
4037
- _$effect((_p$)=>{var _v$6=props.blocked?theme().warning:theme().accent,_v$7=props.blocked?theme().warning:theme().textMuted
4038
- _v$6!==_p$.e&&(_p$.e=_$setProp(_el$8,"fg",_v$6,_p$.e))
4039
- _v$7!==_p$.t&&(_p$.t=_$setProp(_el$9,"fg",_v$7,_p$.t))
4051
+ _$effect((_p$)=>{var _v$3=props.blocked?theme().warning:theme().accent,_v$4=props.blocked?theme().warning:theme().textMuted
4052
+ _v$3!==_p$.e&&(_p$.e=_$setProp(_el$8,"fg",_v$3,_p$.e))
4053
+ _v$4!==_p$.t&&(_p$.t=_$setProp(_el$9,"fg",_v$4,_p$.t))
4040
4054
  return _p$
4041
4055
  },{e:undefined,t:undefined
4042
4056
  })
@@ -4274,7 +4288,7 @@ function SettingsBinding(props){const unregister=props.api.keymap.registerLayer(
4274
4288
  },cmd:`${PLUGIN_ID}.settings`
4275
4289
  }]
4276
4290
  })
4277
- onCleanup12(unregister)
4291
+ onCleanup13(unregister)
4278
4292
  return _$createElement("box")
4279
4293
  }
4280
4294
 
@@ -4302,7 +4316,7 @@ observed=current
4302
4316
  props.preferences.setAutoApprovePermissions(current)
4303
4317
  })
4304
4318
  if(unsubscribe)
4305
- onCleanup13(unsubscribe)
4319
+ onCleanup14(unsubscribe)
4306
4320
  return _$createElement("box")
4307
4321
  }
4308
4322
 
@@ -4474,17 +4488,17 @@ function StartupSessionBinding(props){if(props.api.keymap.on){const unregister=p
4474
4488
  return
4475
4489
  queueMicrotask(()=>void props.controller.start())
4476
4490
  })
4477
- onCleanup14(unregister)
4491
+ onCleanup15(unregister)
4478
4492
  } else
4479
4493
  createEffect12(()=>{if(!props.controller.enabled())
4480
4494
  return
4481
4495
  const unregister=props.api.keymap.registerLayer({mode:"global",priority:2001,commands:[{name:"session.new",title:"New session",category:"Session",namespace:"palette",enabled:props.controller.enabled,run:()=>void props.controller.start()
4482
4496
  }]
4483
4497
  })
4484
- onCleanup14(unregister)
4498
+ onCleanup15(unregister)
4485
4499
  })
4486
4500
  onMount4(()=>void props.controller.initialize())
4487
- onCleanup14(props.controller.dispose)
4501
+ onCleanup15(props.controller.dispose)
4488
4502
  return _$createElement("box")
4489
4503
  }
4490
4504
  function versionParts(value){const match=/^v?(\d+)\.(\d+)\.(\d+)(?:-([^+]+))?/.exec(value)
@@ -4618,7 +4632,7 @@ if(entries.length===0||disposed)
4618
4632
  return
4619
4633
  show()
4620
4634
  } catch{}})
4621
- onCleanup15(()=>{disposed=true
4635
+ onCleanup16(()=>{disposed=true
4622
4636
  clearTimeout(pending)
4623
4637
  })
4624
4638
  return _$createElement("box")
@@ -5014,7 +5028,7 @@ const colors=()=>sidebarInteractiveColors(api.theme.current,{focused:focused(),h
5014
5028
  const ref=(renderable)=>{unregister?.()
5015
5029
  unregister=interaction?.register({...descriptor,renderable})
5016
5030
  }
5017
- onCleanup16(()=>unregister?.())
5031
+ onCleanup17(()=>unregister?.())
5018
5032
  return{ref,disabled,focused,backgroundColor:()=>colors().backgroundColor,foregroundColor:()=>colors().foregroundColor,onMouseOver:()=>setHovered(true),onMouseOut:()=>setHovered(false),select(event){interaction?.mouseSelect(descriptor.id,event)
5019
5033
  },activate(event){if(interaction)
5020
5034
  return interaction.mouseActivate(descriptor.id,event)
@@ -5676,7 +5690,7 @@ props.interaction.leaveFilter(input)
5676
5690
  else
5677
5691
  input?.blur()
5678
5692
  }
5679
- onCleanup17(()=>unregisterInput?.())
5693
+ onCleanup18(()=>unregisterInput?.())
5680
5694
  return _$createComponent(SelectionBox,{ref:(node)=>item.ref(node),get id(){return props.id
5681
5695
  },flexDirection:"row",gap:1,marginTop:1,marginBottom:1,paddingLeft:1,paddingRight:1,get backgroundColor(){return _$memo(()=>!!highlighted())()?theme().primary:item.backgroundColor()
5682
5696
  },get onMouseOver(){return item.onMouseOver
@@ -5886,6 +5900,7 @@ return _$createComponent(SectionWithHeaderAction,{get api(){return props.api
5886
5900
 
5887
5901
 
5888
5902
 
5903
+
5889
5904
 
5890
5905
 
5891
5906
  var ToolbarContext=createContext4()
@@ -5924,7 +5939,7 @@ function QuickActionRow(props){const{api,interaction
5924
5939
  const icons=useIcons()
5925
5940
  const toolbar=useToolbar()
5926
5941
  const theme=()=>api.theme.current
5927
- const [width,setWidth]=createSignal30(0)
5942
+ const [width,setWidth]=createSignal31(0)
5928
5943
  const shortcut=createMemo12(()=>{const bindings=api.keymap.getCommandBindings({visibility:"registered",commands:[props.action.command]
5929
5944
  })
5930
5945
  return api.keys.formatBindings(bindings.get(props.action.command))
@@ -6082,7 +6097,7 @@ untrack6(()=>shortcuts.bind(key))
6082
6097
  createEffect15(()=>{if(shortcuts.active()&&(props.api.route.current.name!=="session"||props.api.ui.dialog.open||props.api.mode.current()==="modal"))
6083
6098
  untrack6(shortcuts.cancel)
6084
6099
  })
6085
- onCleanup18(shortcuts.dispose)
6100
+ onCleanup19(shortcuts.dispose)
6086
6101
  return(()=>{var _el$=_$createElement("box")
6087
6102
  _$setProp(_el$,"position","absolute")
6088
6103
  _$setProp(_el$,"top",0)
@@ -6104,11 +6119,11 @@ const unregisterCommands=props.api.keymap.registerLayer({commands:[...commands,.
6104
6119
  })=>({name:name.replace(PLUGIN_ID,()=>LEGACY_PLUGIN_ID),run,enabled
6105
6120
  }))]
6106
6121
  })
6107
- onCleanup18(unregisterCommands)
6122
+ onCleanup19(unregisterCommands)
6108
6123
  createEffect15(()=>{const unregister=props.api.keymap.registerLayer({bindings:[{key:props.preferences.focusKey(),cmd:FOCUS_COMMAND
6109
6124
  }]
6110
6125
  })
6111
- onCleanup18(unregister)
6126
+ onCleanup19(unregister)
6112
6127
  })
6113
6128
  return _$createElement("box")
6114
6129
  }
@@ -6145,7 +6160,7 @@ createEffect16(()=>{if(!needsSkills())
6145
6160
  return
6146
6161
  const current=target()
6147
6162
  const release=untrack7(()=>props.skills.activate(current))
6148
- onCleanup19(release)
6163
+ onCleanup20(release)
6149
6164
  untrack7(()=>{props.skills.refresh(current).catch(()=>{})
6150
6165
  })
6151
6166
  })
@@ -6186,12 +6201,12 @@ return{pins,view,run}}
6186
6201
 
6187
6202
 
6188
6203
  function ToolbarButton(props){const icons=useIcons()
6189
- const [hovered,setHovered]=createSignal31(false)
6204
+ const [hovered,setHovered]=createSignal32(false)
6190
6205
  const item=useSidebarItem(props.api,props.interaction,{id:props.id,position:()=>({section:0,row:props.row + 1,column:props.column
6191
6206
  }),activate:props.onRun
6192
6207
  },()=>props.reason?props.api.theme.current.textMuted:props.api.theme.current.accent,"control")
6193
6208
  createEffect17(()=>props.onDetails(props.id,item.focused()||hovered()))
6194
- onCleanup20(()=>props.onDetails(props.id,false))
6209
+ onCleanup21(()=>props.onDetails(props.id,false))
6195
6210
  return _$createComponent(IconControl,{get id(){return props.id
6196
6211
  },ref(r$){var _ref$=item.ref
6197
6212
  typeof _ref$==="function"?_ref$(r$):item.ref=r$
@@ -6207,8 +6222,8 @@ setHovered(false)
6207
6222
  }
6208
6223
  function SidebarToolbar(props){const model=createToolbarModel(props)
6209
6224
  const icons=useIcons()
6210
- const [width,setWidth]=createSignal32(3)
6211
- const [detail,setDetail]=createSignal32()
6225
+ const [width,setWidth]=createSignal33(3)
6226
+ const [detail,setDetail]=createSignal33()
6212
6227
  const keys=createMemo15(()=>model.pins().map((pin)=>toolbarPinKey(pin)))
6213
6228
  const layout=createMemo15(()=>toolbarLayout(keys(),width(),props.preferences.toolbarOverflow?.()??"menu"))
6214
6229
  const rows=createMemo15(()=>layout().rows.map((_,index)=>index))
@@ -6289,7 +6304,7 @@ const visibility=createListVisibility({items:filtered,limit:()=>props.preference
6289
6304
  const state=createMemo16(()=>props.controller.state(target()))
6290
6305
  createEffect18(()=>{const current=target()
6291
6306
  const deactivate=untrack8(()=>props.controller.activate?.(current)??(()=>{}))
6292
- onCleanup21(deactivate)
6307
+ onCleanup22(deactivate)
6293
6308
  untrack8(()=>void props.controller.refresh(current).catch(()=>{}))
6294
6309
  })
6295
6310
  async function useSkill(item){try{const isInserted=await props.controller.use(target(),item.name)
@@ -6350,7 +6365,7 @@ return _el$
6350
6365
  }
6351
6366
  function SkillsSection(props){const dialogs=useDialogs(props.api)
6352
6367
  const toolbar=useToolbar()
6353
- const [query,setQuery]=createSignal33("")
6368
+ const [query,setQuery]=createSignal34("")
6354
6369
  const model=createSkillSectionModel(props,query)
6355
6370
  const navigationSection=()=>props.navigationSection??3
6356
6371
  function selectSkill(item){activateSkill({api:props.api,controller:props.controller,access:props.preferences,dialogs,skill:item,onConfigure:toolbar?(navigation)=>toolbar.configure({kind:"skill",name:item.name,location:item.location,icon:"book"
@@ -6430,8 +6445,8 @@ return _el$
6430
6445
  function createSubagentSectionModel(props){const list=createMemo17(()=>props.controller.list(props.sessionID))
6431
6446
  const recent=createMemo17(()=>props.controller.recent?.(props.sessionID)??[])
6432
6447
  const rows=createMemo17(()=>buildSubagentView(list(),recent()))
6433
- const [query,setQuery]=createSignal34("")
6434
- const [mode,setMode]=createSignal34("all")
6448
+ const [query,setQuery]=createSignal35("")
6449
+ const [mode,setMode]=createSignal35("all")
6435
6450
  const counts=createMemo17(()=>countSubagentView(rows(),query()))
6436
6451
  const tabs=createMemo17(()=>["all","active","recent",...counts().errors>0?["errors"]:[]])
6437
6452
  const filtered=createMemo17(()=>filterSubagentView(rows(),mode(),query()))
@@ -6454,7 +6469,7 @@ const state=createMemo17(()=>props.controller.state(props.sessionID))
6454
6469
  createEffect19(()=>{const sessionID=props.sessionID
6455
6470
  targetKey()
6456
6471
  const deactivate=untrack9(()=>props.controller.activate?.(sessionID)??(()=>{}))
6457
- onCleanup22(deactivate)
6472
+ onCleanup23(deactivate)
6458
6473
  untrack9(()=>void props.controller.refresh(sessionID).catch(()=>{}))
6459
6474
  })
6460
6475
  return{list,recent,rows,query,setQuery,mode,setMode,counts,tabs,filtered,parentID,visibility,now,byId,ids,state
@@ -6823,7 +6838,7 @@ const icons=useIcons()
6823
6838
  const unavailable=()=>hostCapabilityUnavailable(props.api,"todo")
6824
6839
  const managed=()=>("guidance" in props.controller)?props.controller:undefined
6825
6840
  const native=()=>("source" in props.controller)&&props.controller.source==="native"
6826
- const [mode,setMode]=createSignal35("all")
6841
+ const [mode,setMode]=createSignal36("all")
6827
6842
  const targetKey=createMemo18(()=>props.controller.target?.(props.sessionID).key??props.sessionID)
6828
6843
  createEffect20(()=>{targetKey()
6829
6844
  setMode("all")
@@ -6837,7 +6852,7 @@ targetKey()
6837
6852
  if(unavailable())
6838
6853
  return
6839
6854
  const deactivate=untrack10(()=>props.controller.activate?.(sessionID)??(()=>{}))
6840
- onCleanup23(deactivate)
6855
+ onCleanup24(deactivate)
6841
6856
  untrack10(()=>void props.controller.refresh(sessionID).catch(()=>{}))
6842
6857
  })
6843
6858
  const navigationSection=()=>props.navigationSection??1
@@ -6901,7 +6916,7 @@ return _el$4
6901
6916
  }
6902
6917
  function SidebarContent(props){const sections=props.preferences.sections
6903
6918
  const visibleSections=createMemo19(()=>props.preferences.sectionOrder().filter((section)=>sections()[section]&&supportsSidebarSection(props.api,section)))
6904
- onCleanup24(()=>props.interaction.setContentRoot(undefined))
6919
+ onCleanup25(()=>props.interaction.setContentRoot(undefined))
6905
6920
  return _$createComponent(ToolbarProvider,{get api(){return props.api
6906
6921
  },get preferences(){return props.preferences
6907
6922
  },get skills(){return props.skills
@@ -6961,7 +6976,7 @@ const params="params" in route?route.params:undefined
6961
6976
  if(typeof params?.sessionID!=="string")
6962
6977
  return
6963
6978
  const current=props.controller.target()
6964
- onCleanup25(()=>props.controller.deactivate(current))
6979
+ onCleanup26(()=>props.controller.deactivate(current))
6965
6980
  untrack11(()=>void props.controller.activate(current).catch(()=>{}))
6966
6981
  })
6967
6982
  return _$createElement("box")
@@ -7001,7 +7016,7 @@ const settingsId=`${PLUGIN_ID}.settings`
7001
7016
  const settings=useSidebarItem(props.api,props.interaction,{id:settingsId,position:{section:0,row:0,column:0
7002
7017
  },activate:()=>openSettings(props.api,props.preferences,undefined,props.mcp,props.skills)
7003
7018
  },()=>theme().textMuted,"control")
7004
- onCleanup26(()=>props.interaction.setTitleRoot(undefined))
7019
+ onCleanup27(()=>props.interaction.setTitleRoot(undefined))
7005
7020
  return(()=>{var _el$=_$createElement("box"),_el$2=_$createElement("box"),_el$3=_$createElement("box")
7006
7021
  _$insertNode(_el$,_el$2)
7007
7022
  _$use((node)=>props.interaction.setTitleRoot(node),_el$)
@@ -7086,9 +7101,9 @@ await options.accountSource.refresh(model)
7086
7101
  if(!disposed&&target===targetKey())
7087
7102
  await options.controller.refresh()
7088
7103
  }
7089
- createEffect22(on2(interval,(milliseconds)=>{onCleanup27(schedule(()=>void refresh().catch(()=>{}),milliseconds))
7104
+ createEffect22(on2(interval,(milliseconds)=>{onCleanup28(schedule(()=>void refresh().catch(()=>{}),milliseconds))
7090
7105
  }))
7091
- onCleanup27(()=>{disposed=true
7106
+ onCleanup28(()=>{disposed=true
7092
7107
  })
7093
7108
  }
7094
7109
  function LimitsPersistence(props){createLimitsRefresh(props)
@@ -7098,20 +7113,16 @@ import {homedir} from "os"
7098
7113
 
7099
7114
  function location(input){return input.directory?{location:{directory:input.directory}}:{}}
7100
7115
  function mcpItem(server){return{name:server.name,status:server.status.status,...server.status.error&&{error:server.status.error}}}
7101
- function skillKey(directory,name){return `${directory}\x00${name}`
7102
- }
7103
- function createV2ClientAdapter(context){const skillIDs=new Map
7104
- return{state:{mcp:()=>(context.data.location.mcp.server.list(context.location)??[]).map((server)=>mcpItem(server))
7116
+ function createV2ClientAdapter(context){return{state:{mcp:()=>(context.data.location.mcp.server.list(context.location)??[]).map((server)=>mcpItem(server))
7105
7117
  },client:{app:{async skills(input,options={}){const response=await context.client.skill.list(location(input),{signal:options.signal})
7106
- const directory=input.directory??response.location.directory
7107
- const data=response.data.map((skill)=>{skillIDs.set(skillKey(directory,skill.name),skill.id)
7108
- return{name:skill.name,description:skill.description,location:skill.path,content:skill.content
7109
- }})
7118
+ const data=response.data.map((skill)=>({name:skill.name,description:skill.description,location:skill.path,content:skill.content
7119
+ }))
7110
7120
  return{data}}},tui:{async appendPrompt(input,options={}){const route=context.ui.router.current()
7111
7121
  const name=/^\/([^\s]+)\s*$/u.exec(input.text)?.[1]
7112
7122
  const directory=input.directory??context.location?.directory??context.data.location.default().directory
7113
7123
  const session=route.type==="session"?context.data.session.get(route.sessionID):undefined
7114
- const id=name?skillIDs.get(skillKey(directory,name)):undefined
7124
+ const candidates=context.data.location.skill.list({directory})?.filter((skill)=>skill.name===name)??[]
7125
+ const id=candidates.length===1?candidates[0].id:undefined
7115
7126
  if(route.type!=="session"||session?.location.directory!==directory||!id)
7116
7127
  return{data:false}
7117
7128
  await context.client.session.skill({sessionID:route.sessionID,id},{signal:options.signal})
@@ -7129,77 +7140,79 @@ if(name!=="session.status"&&(name!=="session.idle"||started))
7129
7140
  return
7130
7141
  const status=started?{type:"busy"}:{type:"idle"}
7131
7142
  return{sessionID,status,properties:{sessionID,...name==="session.status"&&{status}}}}
7132
- var SESSION_UPDATE_EVENTS=new Set(["session.agent.selected","session.model.selected","session.moved","session.renamed","session.permissions","session.metadata.updated"])
7133
- function createV2EventAdapter(context,sessions){return{on(name,handler){let active=true
7134
- const unsubscribe=context.data.listen(({details})=>{const event=details
7135
- const execution=executionStatus(name,event)
7136
- if(execution){sessions.setStatus(execution.sessionID,execution.status)
7137
- handler({type:name,properties:execution.properties
7138
- })
7139
- return
7143
+ var updates=["session.agent.selected","session.model.selected","session.moved","session.renamed","session.permissions","session.metadata.updated"]
7144
+ var executions=["session.execution.started","session.execution.succeeded","session.execution.failed","session.execution.interrupted"]
7145
+ function eventTypes(name){if(name==="session.updated")
7146
+ return updates
7147
+ if(name==="session.status")
7148
+ return ["session.status","session.retry.scheduled",...executions]
7149
+ if(name==="session.idle")
7150
+ return ["session.idle",...executions.slice(1)]
7151
+ if(name==="session.error")
7152
+ return ["session.execution.failed"]
7153
+ if(name==="mcp.tools.changed")
7154
+ return ["mcp.status.changed","mcp.resources.changed"]
7155
+ if(["session.created","session.deleted","server.connected","installation.update-available"].includes(name))
7156
+ return [name]
7157
+ return []
7140
7158
  }
7141
- if(name==="server.connected"&&event.type==="server.connected"){handler({type:name,properties:{}})
7142
- return
7159
+ function statusFor(name,event){if(name==="session.idle")
7160
+ return{type:"idle"}
7161
+ if(event.type==="session.retry.scheduled")
7162
+ return{type:"retry",attempt:Number(event.data.attempt),next:Number(event.data.at),message:event.data.error?.message??"Retrying"
7143
7163
  }
7144
- if(name==="mcp.tools.changed"&&event.type.startsWith("mcp.")){handler({type:name,properties:event.data})
7145
- return
7164
+ return event.data.status
7146
7165
  }
7147
- if(name==="installation.update-available"&&event.type==="installation.update-available"){if(typeof event.data.version==="string"){handler({type:name,properties:{version:event.data.version}})
7166
+ function createV2EventAdapter(context,sessions){return{on(name,handler){let active=true
7167
+ const disposers=eventTypes(name).map((type)=>context.data.on(type,(value)=>{const event=value
7168
+ const data=event.data??{}
7169
+ const emit=(properties)=>{if(active)
7170
+ handler({type:name,properties})
7148
7171
  }
7172
+ const execution=executionStatus(name,{type:event.type,data})
7173
+ if(execution){sessions.setStatus(execution.sessionID,execution.status)
7174
+ emit(execution.properties)
7149
7175
  return
7150
7176
  }
7151
- if(name==="session.deleted"&&event.type==="session.deleted"){handler({type:name,properties:{sessionID:event.data.sessionID}})
7177
+ if(name==="server.connected"||name==="mcp.tools.changed"){emit(data)
7152
7178
  return
7153
7179
  }
7154
- if(name==="session.status"&&event.type==="session.status"){const sessionID=event.data.sessionID
7155
- const status=event.data.status
7156
- if(typeof sessionID!=="string"||!status||typeof status!=="object")
7157
- return
7158
- sessions.setStatus(sessionID,status)
7159
- handler({type:name,properties:{sessionID,status}})
7180
+ if(name==="installation.update-available"){if(typeof data.version==="string")
7181
+ emit({version:data.version})
7160
7182
  return
7161
7183
  }
7162
- if(name==="session.idle"&&event.type==="session.idle"){const sessionID=event.data.sessionID
7184
+ const sessionID=data.sessionID
7163
7185
  if(typeof sessionID!=="string")
7164
7186
  return
7165
- sessions.setStatus(sessionID,{type:"idle"})
7166
- handler({type:name,properties:{sessionID}})
7167
- return
7187
+ if(name==="session.status"||name==="session.idle"){const status=statusFor(name,{type:event.type,data})
7188
+ if(status&&typeof status==="object"){sessions.setStatus(sessionID,status)
7189
+ emit(name==="session.idle"?{sessionID}:{sessionID,status})
7168
7190
  }
7169
- if(name==="session.error"&&event.type==="session.execution.failed"){const error=event.data.error
7170
- handler({type:name,properties:{sessionID:event.data.sessionID,error:error&&{name:error.type??"Error",data:{message:error.message??"Session error"}}}})
7171
7191
  return
7172
7192
  }
7173
- const isCreated=name==="session.created"&&event.type==="session.created"
7174
- const isUpdated=name==="session.updated"&&SESSION_UPDATE_EVENTS.has(event.type)
7175
- if(!isCreated&&!isUpdated)
7193
+ if(name==="session.deleted"){emit({sessionID})
7176
7194
  return
7177
- const sessionID=event.data.sessionID
7178
- const info=typeof sessionID==="string"?context.data.session.get(sessionID):undefined
7179
- if(info){handler({type:name,properties:{info:sessions.normalize(info)}})
7195
+ }
7196
+ if(name==="session.error"){const error=data.error
7197
+ emit({sessionID,error:error&&{name:error.type??"Error",data:{message:error.message??"Session error"}}})
7180
7198
  return
7181
7199
  }
7182
- if(typeof sessionID!=="string")
7200
+ const info=context.data.session.get(sessionID)
7201
+ if(info){emit({info:sessions.project(info)})
7183
7202
  return
7203
+ }
7184
7204
  context.data.session.sync(sessionID).then(()=>{const synced=context.data.session.get(sessionID)
7185
- if(active&&synced)
7186
- handler({type:name,properties:{info:sessions.normalize(synced)}})
7205
+ if(synced)
7206
+ emit({info:sessions.project(synced)})
7187
7207
  }).catch(()=>{})
7188
- })
7208
+ }))
7189
7209
  return()=>{active=false
7190
- unsubscribe()
7210
+ for(const dispose of disposers)
7211
+ dispose()
7191
7212
  }}}}
7192
7213
 
7193
-
7194
7214
  function enabled(value){return typeof value==="function"?value():value!==false
7195
7215
  }
7196
- function belongsTo2(renderable,ancestor){if(!renderable||!ancestor)
7197
- return false
7198
- for(let current=renderable;current; current=current.parent){if(current===ancestor)
7199
- return true
7200
- }
7201
- return false
7202
- }
7203
7216
  function v2LayerCommands(id,layer,available,run,dispatch){const named=(layer.commands??[]).map((command)=>({id:command.name,bind:command.name==="session.new"?"<leader>n":undefined,title:command.title,description:command.desc,group:command.category,palette:command.namespace==="palette"?true:undefined,suggested:command.suggested,enabled:()=>available()&&enabled(command.enabled),run:(input,event)=>run(command,input,event)
7204
7217
  }))
7205
7218
  const bindings=(layer.bindings??[]).flatMap((binding,index)=>typeof binding.key==="string"?[{id:`${PLUGIN_ID}.v2-binding.${id}.${index}`,bind:binding.key,enabled:available,run:()=>dispatch(String(binding.cmd))
@@ -7208,98 +7221,112 @@ return [...named,...bindings]
7208
7221
  }
7209
7222
  var SETTINGS_COMMAND="opencode.settings"
7210
7223
  var PERMISSIONS_ACTION="permission.mode"
7211
- function createV2Keymap(context){const [revision,setRevision]=createSignal36(0)
7212
- const layers=new Map
7224
+ function createV2Keymap(context){const cleanups=new Set
7213
7225
  let sequence=0
7214
- function layerActive(layer){const mode=layer.mode??"base"
7215
- const target=layer.target
7216
- return(mode==="global"||context.keymap.mode.current()===mode)&&enabled(layer.enabled)&&(!target||belongsTo2(context.renderer.currentFocusedRenderable,target))
7217
- }
7218
- function legacyCommand(name){return [...layers].sort(([leftID,left],[rightID,right])=>(right.priority??0)-(left.priority??0)||rightID-leftID).flatMap(([,layer])=>layerActive(layer)?[layer]:[]).flatMap((layer)=>layer.commands??[]).find((command)=>command.name===name&&enabled(command.enabled))
7219
- }
7220
- function run(command,input="",event){return command.run({keymap,event,focused:context.renderer.currentFocusedRenderable,target:context.renderer.currentFocusedRenderable,data:{},command,input,payload:undefined
7226
+ async function run(command,input="",event){await command.run({keymap,event,focused:context.renderer.currentFocusedRenderable,target:context.renderer.currentFocusedRenderable,data:{},command,input,payload:undefined
7221
7227
  })
7222
7228
  }
7223
- async function runV2(command,input="",event){await run(command,input,event)
7224
- }
7225
7229
  const onKeypress=(event)=>{if(event.name!==","||!event.ctrl||event.shift||event.meta||event.super||event.hyper)
7226
7230
  return
7227
- const command=legacyCommand(`${PLUGIN_ID}.settings`)
7228
- if(!command)
7231
+ const command=context.keymap.commands().find((item)=>item.id===`${PLUGIN_ID}.settings`)
7232
+ if(!command||!enabled(command.enabled))
7229
7233
  return
7230
7234
  event.preventDefault()
7231
7235
  event.stopPropagation()
7232
- runV2(command,"",event)
7236
+ context.keymap.dispatch(command.id)
7233
7237
  }
7234
7238
  context.renderer.keyInput.on("keypress",onKeypress)
7235
- function layerCommands([id,layer]){return v2LayerCommands(id,layer,()=>layerActive(layer),runV2,context.keymap.dispatch)
7239
+ const keymap={registerLayer(layer){const id=++sequence
7240
+ const owner=getOwner()
7241
+ const [active,setActive]=createSignal37(true)
7242
+ const install=()=>{context.keymap.layer(()=>({mode:typeof layer.mode==="string"?layer.mode:"base",priority:layer.priority,enabled:()=>active()&&enabled(layer.enabled),target:layer.target?()=>layer.target:undefined,commands:v2LayerCommands(id,layer,()=>true,run,context.keymap.dispatch)
7243
+ }))
7236
7244
  }
7237
- function Bridge(){context.keymap.layer(()=>{revision()
7238
- const current=[...layers].sort(([leftID,left],[rightID,right])=>(right.priority??0)-(left.priority??0)||rightID-leftID)
7239
- const commands=current.flatMap((entry)=>layerCommands(entry))
7240
- const bindings=commands.flatMap((command)=>command.id?[command.id]:[])
7241
- return{mode:"global",priority:2000,commands,bindings
7242
- }})
7243
- return _$createElement("box")
7245
+ let release
7246
+ if(owner)
7247
+ release=createRoot((cleanup)=>{install()
7248
+ return cleanup
7249
+ },owner)
7250
+ else
7251
+ install()
7252
+ const dispose=()=>{setActive(false)
7253
+ release?.()
7244
7254
  }
7245
- const keymap={registerLayer(layer){const id=++sequence
7246
- layers.set(id,layer)
7247
- setRevision((value)=>value + 1)
7248
- return()=>{if(!layers.delete(id))
7249
- return
7250
- setRevision((value)=>value + 1)
7251
- }},dispatchCommand(name,options){const command=context.keymap.commands().find((candidate)=>candidate.id===(name===PERMISSIONS_ACTION?SETTINGS_COMMAND:name))
7252
- if(command){if(!enabled(command.enabled))
7255
+ cleanups.add(dispose)
7256
+ return()=>{cleanups.delete(dispose)
7257
+ dispose()
7258
+ }},dispatchCommand(name,options){const target=options?.focused??options?.target
7259
+ if(target?.parent&&!target.isDestroyed)
7260
+ target.focus()
7261
+ const command=context.keymap.commands().find((item)=>item.id===(name===PERMISSIONS_ACTION?SETTINGS_COMMAND:name))
7262
+ if(!command)
7263
+ return{ok:false,reason:"not-found"
7264
+ }
7265
+ if(!enabled(command.enabled))
7253
7266
  return{ok:false,reason:"disabled",command
7254
7267
  }
7255
7268
  context.keymap.dispatch(command.id,typeof options?.payload==="string"?options.payload:undefined)
7256
7269
  return{ok:true,command
7257
- }}
7258
- const fallback=legacyCommand(name)
7259
- if(!fallback)
7260
- return{ok:false,reason:"not-found"
7261
- }
7262
- run(fallback)
7263
- return{ok:true,command:fallback
7264
7270
  }},getCommands(){const commands=context.keymap.commands()
7265
- const result=commands.flatMap((command)=>{if(!command.id)
7266
- return []
7267
- const name=command.id
7268
- return [{...command,name,run:()=>context.keymap.dispatch(name)
7269
- }]
7270
- })
7271
- const settings=commands.find((command)=>command.id===SETTINGS_COMMAND)
7271
+ const result=commands.flatMap((command)=>command.id?[{...command,name:command.id,run:()=>context.keymap.dispatch(command.id)
7272
+ }]:[])
7273
+ const settings=commands.find((item)=>item.id===SETTINGS_COMMAND)
7272
7274
  if(settings)
7273
7275
  result.push({...settings,id:PERMISSIONS_ACTION,name:PERMISSIONS_ACTION,title:"Permissions settings",run:()=>context.keymap.dispatch(SETTINGS_COMMAND)
7274
7276
  })
7275
7277
  return result
7276
7278
  },getCommandBindings(input){return new Map(input.commands.map((name)=>[name,[...context.keymap.shortcuts(name)]]))
7277
- },parseKeySequence(value){return value.trim()?value.trim().split(/\s+/u):[]
7278
7279
  }}
7279
- return{keymap,Bridge,dispose:()=>{context.renderer.keyInput.off("keypress",onKeypress)
7280
+ return{keymap,dispose(){for(const cleanup of cleanups)
7281
+ cleanup()
7282
+ cleanups.clear()
7283
+ context.renderer.keyInput.off("keypress",onKeypress)
7280
7284
  }}}
7281
- function createV2SessionAdapter(context){const statuses=new Map
7285
+
7286
+ function createV2SessionAdapter(context){const [retryDetails,setRetryDetails]=createSignal38({})
7287
+ const projections=new WeakMap
7282
7288
  function normalize(info){const directory=info.location.directory
7283
7289
  const workspaceID=context.location?.directory===directory?context.location.workspaceID:undefined
7284
7290
  return{id:info.id,slug:info.id,projectID:info.projectID,...workspaceID&&{workspaceID},directory,...info.parentID&&{parentID:info.parentID},cost:Number(info.cost),tokens:info.tokens,title:info.title??"Untitled session",...info.agent&&{agent:info.agent},...info.model&&{model:info.model},version:context.app.version,metadata:info.metadata,time:{created:info.time.created,updated:info.time.updated,...info.time.archived&&{archived:info.time.archived}}}}
7285
- function status(sessionID){const cached=statuses.get(sessionID)
7286
- if(cached)
7287
- return cached
7288
- return context.data.session.status(sessionID)==="running"?{type:"busy"}:{type:"idle"}}
7289
- return{normalize,setStatus(sessionID,value){statuses.set(sessionID,value)
7291
+ function status(sessionID){if(context.data.session.status(sessionID)!=="running")
7292
+ return{type:"idle"}
7293
+ const messages=context.data.session.message?.list?.(sessionID)??[]
7294
+ let message
7295
+ for(let index=messages.length-1;index>=0; index--){const candidate=messages[index]
7296
+ if(candidate.type==="assistant"&&!candidate.time.completed){message=candidate
7297
+ break
7298
+ }}
7299
+ if(message?.type==="assistant"&&message.retry)
7300
+ return{type:"retry",attempt:message.retry.attempt,next:message.retry.at,message:message.retry.error.message
7301
+ }
7302
+ return retryDetails()[sessionID]??{type:"busy"}}
7303
+ function project(info){let value=projections.get(info)
7304
+ if(!value){value=normalize(info)
7305
+ for(const key of ["id","slug","projectID","workspaceID","directory","parentID","cost","tokens","title","agent","model","version","metadata","time"])
7306
+ Object.defineProperty(value,key,{enumerable:true,get:()=>normalize(info)[key]})
7307
+ projections.set(info,value)
7308
+ }
7309
+ return value
7310
+ }
7311
+ return{normalize,project,status,setStatus(sessionID,value){setRetryDetails((current)=>{const next={...current}
7312
+ if(value.type==="retry")
7313
+ next[sessionID]=value
7314
+ else
7315
+ delete next[sessionID]
7316
+ return next
7317
+ })
7290
7318
  },state:{count:()=>context.data.session.list().length,get:(sessionID)=>{const value=context.data.session.get(sessionID)
7291
- return value?normalize(value):undefined
7319
+ return value?project(value):undefined
7292
7320
  },todo:()=>[],status
7293
7321
  },client:{async create(input,options={}){const directory=input.directory??context.location?.directory??context.data.location.default().directory
7294
7322
  const session=await context.client.session.create({location:{directory}},{signal:options.signal})
7295
7323
  return{data:normalize(session)}},async children(input,options={}){const response=await context.client.session.list({parentID:input.sessionID,...input.directory&&{directory:input.directory}},{signal:options.signal})
7296
7324
  return{data:response.data.map((session)=>normalize(session))}},async status(_input,options={}){const active=await context.client.session.active({signal:options.signal})
7297
- const data=Object.fromEntries(context.data.session.list().map((session)=>[session.id,statuses.get(session.id)??(active[session.id]?{type:"busy"}:{type:"idle"})]))
7325
+ const data=Object.fromEntries(context.data.session.list().map((session)=>[session.id,active[session.id]?retryDetails()[session.id]??{type:"busy"}:{type:"idle"}]))
7298
7326
  return{data}},async todo(){return{data:[]}}}}}
7299
7327
 
7300
-
7301
- function createV2Slots(context,keymap,onDispose){return{register(plugin){const slots=plugin.slots
7302
- const cleanups=[context.ui.slot({append:"app",render:()=>[_$createComponent(keymap.Bridge,{}),_$memo(()=>slots.app?.(undefined,{}))]
7328
+ function createV2Slots(context,onDispose){return{register(plugin){const slots=plugin.slots
7329
+ const cleanups=[context.ui.slot({append:"app",render:()=>_$memo(()=>slots.app?.(undefined,{}))
7303
7330
  }),context.ui.slot({replace:"sidebar.content",render:(input)=>{const sessionID=input.sessionID
7304
7331
  return slots.sidebar_content?.(undefined,{session_id:sessionID
7305
7332
  })
@@ -7321,7 +7348,7 @@ return context.data.session.message.list(route.sessionID).some((message)=>(!reve
7321
7348
 
7322
7349
  function createV2UI(context){let dialogOpen=false
7323
7350
  function DialogPrompt(props){let active=true
7324
- onCleanup28(()=>{active=false
7351
+ onCleanup29(()=>{active=false
7325
7352
  })
7326
7353
  onMount6(async ()=>{const value=await context.ui.dialog.prompt({title:props.title,placeholder:props.placeholder,value:props.value
7327
7354
  })
@@ -7335,7 +7362,7 @@ props.onConfirm?.(value)
7335
7362
  return _$createElement("box")
7336
7363
  }
7337
7364
  function DialogConfirm(props){let active=true
7338
- onCleanup28(()=>{active=false
7365
+ onCleanup29(()=>{active=false
7339
7366
  })
7340
7367
  onMount6(async ()=>{const confirmed=await context.ui.dialog.confirm({title:props.title,message:props.message
7341
7368
  })
@@ -7397,10 +7424,10 @@ context.ui.router.navigate({type:"plugin",name,data:params})
7397
7424
  },client:{...remote.client,session:sessions.client
7398
7425
  },event:createV2EventAdapter(context,sessions),theme:{get current(){return legacyTheme(context.theme)
7399
7426
  },ready:true,mode:()=>context.themeMode
7400
- },attention:context.attention,lifecycle:{signal:controller.signal,onDispose},slots:createV2Slots(context,keymap,(dispose)=>{onDispose(dispose)
7427
+ },attention:context.attention,lifecycle:{signal:controller.signal,onDispose},slots:createV2Slots(context,(dispose)=>{onDispose(dispose)
7401
7428
  })
7402
7429
  }
7403
- setHostCapabilities(api,{todo:true,lsp:false,titleVisibility:false,permissionMode:false
7430
+ setHostCapabilities(api,{todo:true,lsp:false,titleVisibility:false,permissionMode:false,commandsReachableOnly:true
7404
7431
  })
7405
7432
  bindToolbarActionState(context,api)
7406
7433
  return{api,async dispose(){controller.abort()
@@ -7470,28 +7497,91 @@ activeTarget=undefined
7470
7497
  requests.abortAll()
7471
7498
  pending.clear()
7472
7499
  }}}}
7500
+ function collectionLocation(target){return{directory:target.routing.directory??"",...target.routing.workspace&&{workspaceID:target.routing.workspace}}}
7501
+ async function syncCollection(collection,target,force,signal){signal.throwIfAborted()
7502
+ const ref=collectionLocation(target)
7503
+ if(force)
7504
+ collection.invalidate(ref)
7505
+ await collection.sync(ref)
7506
+ signal.throwIfAborted()
7507
+ }
7508
+ function createV2McpSource(context){const collection=context.data.location.mcp.server
7509
+ const projections=new WeakMap
7510
+ const list=(target)=>(collection.list(collectionLocation(target))??[]).map((server)=>{let item=projections.get(server)
7511
+ if(!item){item={get name(){return server.name
7512
+ },get title(){return server.title
7513
+ },get status(){return server.status.status
7514
+ },get error(){return "error" in server.status?server.status.error:undefined
7515
+ }}
7516
+ projections.set(server,item)
7517
+ }
7518
+ return item
7519
+ })
7520
+ return{list,async refresh(target,force,signal){await syncCollection(collection,target,force,signal)
7521
+ return list(target)
7522
+ }}}
7523
+ function createV2SkillSource(context){const projections=new WeakMap
7524
+ const collection=context.data.location.skill
7525
+ const list=(target)=>(collection.list(collectionLocation(target))??[]).map((skill)=>{let item=projections.get(skill)
7526
+ if(!item){item={get id(){return skill.id
7527
+ },get name(){return skill.name
7528
+ },get description(){return skill.description
7529
+ },get location(){return skill.path
7530
+ },get content(){return skill.content
7531
+ }}
7532
+ projections.set(skill,item)
7533
+ }
7534
+ return item
7535
+ })
7536
+ return{list,async refresh(target,force,signal){await syncCollection(collection,target,force,signal)
7537
+ return list(target)
7538
+ },async use(target,skill,signal){signal.throwIfAborted()
7539
+ const route=context.ui.router.current()
7540
+ const session=route.type==="session"&&context.data.session.get(route.sessionID)
7541
+ const actual=list(target).find((item)=>item.id===skill.id&&item.location===skill.location)
7542
+ if(route.type!=="session"||!session||session.location.directory!==target.routing.directory||!actual?.id)
7543
+ throw new Error("Skill target changed; select the source again")
7544
+ await context.client.session.skill({sessionID:route.sessionID,id:actual.id},{signal})
7545
+ return !signal.aborted
7546
+ }}}
7547
+ function createV2SubagentSource(context,api){return{list:(parentID)=>context.data.session.list().filter((item)=>item.parentID===parentID).flatMap((item)=>{const value=api.state.session.get(item.id)
7548
+ return value?[value]:[]
7549
+ }),status:(id)=>api.state.session.status(id)??{type:"idle"},async refresh(parentID,signal){signal.throwIfAborted()
7550
+ let cursor
7551
+ do{const page=await context.client.session.list(cursor?{cursor,limit:100}:{parentID,limit:100},{signal
7552
+ })
7553
+ await Promise.all(page.data.filter((item)=>!context.data.session.get(item.id)).map((item)=>context.data.session.sync(item.id)))
7554
+ cursor=page.cursor.next??undefined
7555
+ signal.throwIfAborted()
7556
+ } while(cursor)
7557
+ }}}
7558
+ function createOpenCodeV2Sources(context,api){return{skills:createV2SkillSource(context),mcp:createV2McpSource(context),subagents:createV2SubagentSource(context,api)
7559
+ }}
7473
7560
  function loadNavigatorUpdates(){const path=import.meta.url.includes("/dist/tui.js")?"./navigator-updates.js":"../navigator-updates"
7474
7561
  return import(path)
7475
7562
  }
7476
7563
  async function setupNavigator(api,options,readProject,todoController,hostContext){const config=pluginConfig(undefined)
7477
7564
  const configured=await loadConfiguredDefaults(api,options,readProject)
7478
7565
  const todo=todoController??createTodoController(api)
7479
- const subagents=createSubagentController(api)
7480
- const skills=createSkillController(api)
7566
+ const nativeSources=hostContext&&createOpenCodeV2Sources(hostContext,api)
7567
+ const subagents=createSubagentController(api,{source:nativeSources?.subagents
7568
+ })
7569
+ const skills=createSkillController(api,nativeSources?.skills)
7481
7570
  const preferences=createPreferencesController(api,config,createPreferencesStore(api.state.path.state),configured)
7482
7571
  const limitsPath=import.meta.url.includes("/dist/tui.js")?"./provider-limits.js":"../provider-limits"
7483
7572
  const limitsModule=await import(limitsPath)
7484
7573
  const LimitsSection=limitsModule.ProviderLimitsSection
7485
7574
  const{controller:limits,codex,accountSource,selectModel,modelSelection
7486
- }=limitsModule.createProviderQuotaIntegration(api,preferences,"0.22.0",hostContext)
7487
- const mcp=createMcpController(api,preferences.persistMcp,preferences)
7575
+ }=limitsModule.createProviderQuotaIntegration(api,preferences,"0.23.0",hostContext)
7576
+ const mcp=createMcpController(api,preferences.persistMcp,preferences,nativeSources?.mcp)
7488
7577
  const interaction=createSidebarInteraction(api,()=>openKeyboardHelp(api,preferences.lspIconStyle,preferences.cornerFont,sidebarShortcutActions(api)))
7489
7578
  const startup=createStartupSessionController(api,preferences)
7490
7579
  api.lifecycle.onDispose(()=>preferences.flush())
7491
7580
  api.lifecycle.onDispose(()=>limits.dispose())
7492
7581
  api.lifecycle.onDispose(()=>accountSource?.dispose())
7493
7582
  api.lifecycle.onDispose(()=>interaction.dispose())
7494
- const unsubscribeMcp=api.event.on("mcp.tools.changed",()=>{mcp.refresh().catch(()=>{})
7583
+ const unsubscribeMcp=api.event.on("mcp.tools.changed",()=>{if(!nativeSources)
7584
+ mcp.refresh().catch(()=>{})
7495
7585
  })
7496
7586
  const unsubscribeConnected=api.event.on("server.connected",()=>{if(!api.state.ready||!api.kv.ready)
7497
7587
  return
@@ -7511,7 +7601,7 @@ api.slots.register({order:100,slots:{app(){return _$createComponent(IconProvider
7511
7601
  },interaction,skills,subagents,mcp
7512
7602
  }),_$createComponent(FirstRunWizardPersistence,{api,preferences
7513
7603
  }),_$createComponent(StartupSessionBinding,{api,controller:startup
7514
- }),_$createComponent(UpgradeNotesBinding,{api,preferences,version:"0.22.0"
7604
+ }),_$createComponent(UpgradeNotesBinding,{api,preferences,version:"0.23.0"
7515
7605
  }),_$createComponent(McpPersistence,{api,controller:mcp
7516
7606
  })]
7517
7607
  }})
@@ -7544,12 +7634,12 @@ return result.data?.type==="text"?result.data.content:undefined
7544
7634
  })
7545
7635
  const{createVersionStatus,createVersionUpdateActions,createOpenCodeV1VersionUpdater
7546
7636
  }=await loadNavigatorUpdates()
7547
- const versionStatus=createVersionStatus(api,"0.22.0")
7548
- const versionUpdates=createVersionUpdateActions(api,versionStatus,createOpenCodeV1VersionUpdater(api,meta),()=>preferences.rememberNavigatorVersionBeforeUpdate("0.22.0"))
7637
+ const versionStatus=createVersionStatus(api,"0.23.0")
7638
+ const versionUpdates=createVersionUpdateActions(api,versionStatus,createOpenCodeV1VersionUpdater(api,meta),()=>preferences.rememberNavigatorVersionBeforeUpdate("0.23.0"))
7549
7639
  api.slots.register({order:90,slots:{sidebar_footer(_context,props){return _$createComponent(IconProvider,{get style(){return preferences.lspIconStyle
7550
7640
  },get multilineCorners(){return preferences.cornerFont
7551
7641
  },get children(){return _$createComponent(VersionFooter,{api,get sessionID(){return props.session_id
7552
- },navigatorVersion:"0.22.0",status:versionStatus,get onOpenCodeUpdate(){return versionUpdates.openCode
7642
+ },navigatorVersion:"0.23.0",status:versionStatus,get onOpenCodeUpdate(){return versionUpdates.openCode
7553
7643
  },get onNavigatorUpdate(){return versionUpdates.navigator
7554
7644
  }})
7555
7645
  }})
@@ -7565,15 +7655,15 @@ return new TextDecoder().decode(await context.client.file.read({location:context
7565
7655
  },todo,context)
7566
7656
  const{createVersionStatus,createVersionUpdateActions,createOpenCodeV2VersionUpdater
7567
7657
  }=await loadNavigatorUpdates()
7568
- const versionStatus=createVersionStatus(adapter.api,"0.22.0")
7569
- const versionUpdates=createVersionUpdateActions(adapter.api,versionStatus,createOpenCodeV2VersionUpdater(context),()=>preferences.rememberNavigatorVersionBeforeUpdate("0.22.0"))
7658
+ const versionStatus=createVersionStatus(adapter.api,"0.23.0")
7659
+ const versionUpdates=createVersionUpdateActions(adapter.api,versionStatus,createOpenCodeV2VersionUpdater(context),()=>preferences.rememberNavigatorVersionBeforeUpdate("0.23.0"))
7570
7660
  const disposeFooter=context.ui.slot({append:"sidebar.footer",render:()=>_$createComponent(IconProvider,{get style(){return preferences.lspIconStyle
7571
7661
  },get multilineCorners(){return preferences.cornerFont
7572
7662
  },get children(){var _el$=_$createElement("box")
7573
7663
  _$setProp(_el$,"flexDirection","row")
7574
7664
  _$setProp(_el$,"justifyContent","space-between")
7575
7665
  _$insert(_el$,_$createComponent(VersionSummary,{get api(){return adapter.api
7576
- },navigatorVersion:"0.22.0",status:versionStatus,get onOpenCodeUpdate(){return versionUpdates.openCode
7666
+ },navigatorVersion:"0.23.0",status:versionStatus,get onOpenCodeUpdate(){return versionUpdates.openCode
7577
7667
  },get onNavigatorUpdate(){return versionUpdates.navigator
7578
7668
  }}),null)
7579
7669
  _$insert(_el$,_$createComponent(SettingsFooterButton,{get api(){return adapter.api