opencode-navigator 0.17.3 → 0.18.1

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/CHANGELOG.md CHANGED
@@ -2,6 +2,17 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## [0.18.1] - 2026-10-01
6
+
7
+ - Show calm Todo setup guidance in OpenCode 2.x when Navigator's server plugin is absent, without a request error or Retry control. Actual server failures still offer Retry.
8
+ - Keep Quick Action names readable in narrow sidebars by placing unavailable reasons below them and showing shortcuts only when they fit.
9
+
10
+ ## [0.18.0] - 2026-10-01
11
+
12
+ - Add durable per-session Todo lists to OpenCode 2.x through a separately configured Navigator server plugin and an agent Todo tool.
13
+ - Update Todo in the sidebar as tasks change, and offer an off-by-default per-session switch for model-context Todo instructions.
14
+ - Keep Todo visible with setup guidance when the OpenCode 2.x server plugin is missing.
15
+
5
16
  ## [0.17.3] - 2026-09-30
6
17
 
7
18
  ## [0.17.2] - 2026-09-30
@@ -30,6 +41,8 @@
30
41
 
31
42
  Earlier release notes are available on [GitHub Releases](https://github.com/St1ggy/opencode-navigator/releases).
32
43
 
44
+ [0.18.1]: https://github.com/St1ggy/opencode-navigator/compare/v0.18.0...v0.18.1
45
+ [0.18.0]: https://github.com/St1ggy/opencode-navigator/compare/v0.17.3...v0.18.0
33
46
  [0.17.3]: https://github.com/St1ggy/opencode-navigator/compare/v0.17.2...v0.17.3
34
47
  [0.17.2]: https://github.com/St1ggy/opencode-navigator/compare/v0.17.1...v0.17.2
35
48
  [0.17.1]: https://github.com/St1ggy/opencode-navigator/compare/v0.17.0...v0.17.1
package/README.md CHANGED
@@ -12,7 +12,7 @@ See the [changelog](CHANGELOG.md) for release-by-release changes.
12
12
 
13
13
  - Theme-aware session title with the session creation date
14
14
  - First-run setup guide with interactive section settings
15
- - Collapsible Todo section with status filters, grouped tasks, progress and priority indicators
15
+ - Collapsible Todo section with status filters, grouped tasks, progress and priority indicators; OpenCode 2 adds session-owned tasks and optional model-context instructions
16
16
  - Active subagent list with live statuses and click-to-open navigation, including dev-team workers running in separate local processes
17
17
  - Compact, searchable workspace skill list with user-wide favorites, recent skills, source details, and click-to-confirm slash commands
18
18
  - Configurable host quick actions, including the auto-approve toggle, with user-wide bookmarks and availability reasons
@@ -42,8 +42,9 @@ collapsed. Use `Save current layout as default` in sidebar settings to reuse the
42
42
  current visible/hidden, expanded/collapsed, and ordering states in new sessions.
43
43
 
44
44
  Supports OpenCode 1.18.30 and newer, including OpenCode 2.x. OpenCode 2.0.16 does
45
- not expose Todo or LSP data to TUI plugins. Navigator keeps Todo visible with an
46
- explicit unsupported-host message and hides LSP on 2.x while retaining Subagents,
45
+ not expose host Todo or LSP data to TUI plugins. Navigator supplies its own
46
+ session Todo list through a separately configured server plugin in 2.x. Without
47
+ that plugin, Todo stays visible with setup guidance. LSP is hidden on 2.x while retaining Subagents,
47
48
  Skills, Quick Actions, MCP, Search, settings, and presets. All interface icons use Nerd Font glyphs by
48
49
  default (Codicons for controls, keyboard symbols for hints, language logos for LSP). Use a Nerd Fonts v3
49
50
  terminal font, or select **Settings → Behavior → Icon style → Text fallback**.
@@ -168,6 +169,32 @@ OpenCode 2.x CLI plugins are global terminal settings; there is no project-local
168
169
  plugin overrides are required. OpenCode validates `cli.json` strictly, so keep it
169
170
  within the linked schema and restart OpenCode after changing it.
170
171
 
172
+ To enable Todo in OpenCode 2.x, also install the **server** half of Navigator.
173
+ Add the same package to `~/.config/opencode/opencode.json` (or the project
174
+ `opencode.json`):
175
+
176
+ ```json
177
+ {
178
+ "$schema": "https://opencode.ai/config.json",
179
+ "plugin": ["opencode-navigator"]
180
+ }
181
+ ```
182
+
183
+ For a local source installation, use the directory containing Navigator's
184
+ `server.js` in the `plugin` array. Keep the TUI package in `cli.json` as above;
185
+ the server configuration alone does not replace it. Restart OpenCode after
186
+ configuring both. Todo items belong to the current session, survive restarts,
187
+ and update in the sidebar when the agent calls `navigator_todo_write`. The
188
+ **Todo instructions: Off/On** control in the Todo section is session-scoped and
189
+ starts **Off**: tasks and the agent tool work without adding Todo instructions
190
+ to the model's context. OpenCode 1.x continues to show the host's Todo list.
191
+
192
+ If you use both OpenCode 1.x and 2.x with the same global `opencode.json`, keep
193
+ that shared server config compatible with 1.x. Place the server-plugin entry
194
+ in a separate JSON file and set `OPENCODE_CONFIG` to its path **only when
195
+ launching OpenCode 2.x**. The TUI configuration in `cli.json` remains separate;
196
+ the OpenCode 1.x launch must not load Navigator's 2.x server plugin.
197
+
171
198
  ### Install the corner font
172
199
 
173
200
  **Required for rounded multiline selections in Nerd Font mode.** Run the font
@@ -509,8 +536,8 @@ root order. Activate any badge, including a custom server, to toggle its full ID
509
536
  in place. Navigator does not open a root/status dialog because OpenCode may report
510
537
  an empty root and does not expose the underlying diagnostic error text.
511
538
 
512
- Todo priorities are read-only because OpenCode does not expose a Todo mutation
513
- API to TUI plugins.
539
+ Todo priorities are read-only in the sidebar. OpenCode 1.x provides host Todo
540
+ data without a TUI mutation API; on 2.x, Navigator's agent tool writes tasks.
514
541
 
515
542
  Todo offers `All`, `Active`, and `Finished` views with counts. Active shows running
516
543
  tasks before pending tasks. Finished contains separate Completed and Cancelled
package/dist/rpc.js ADDED
@@ -0,0 +1,15 @@
1
+ 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
2
+ }
3
+ var todos={type:"array",items:item,maxItems:100}
4
+ var sessionID={type:"string",minLength:1}
5
+ var output={type:"object",properties:{todos,guidance:{type:"boolean"}},required:["todos","guidance"],additionalProperties:false
6
+ }
7
+ var NavigatorTodoRpc={id:"opencode-navigator.todo",methods:{list:{input:{type:"object",properties:{sessionID},required:["sessionID"],additionalProperties:false},output
8
+ },replace:{input:{type:"object",properties:{sessionID,todos},required:["sessionID","todos"],additionalProperties:false
9
+ },output
10
+ },guidance:{input:{type:"object",properties:{sessionID,enabled:{type:"boolean"}},required:["sessionID","enabled"],additionalProperties:false
11
+ },output
12
+ }},events:{updated:{schema:{type:"object",properties:{sessionID,todos,guidance:{type:"boolean"}},required:["sessionID","todos","guidance"],additionalProperties:false
13
+ }}}}
14
+ export{NavigatorTodoRpc
15
+ }
package/dist/server.js ADDED
@@ -0,0 +1,72 @@
1
+ import {Plugin} from "@opencode/plugin"
2
+ function normalizeTodoState(value){if(!value||typeof value!=="object")
3
+ return{todos:[],guidance:false}
4
+ const state=value
5
+ const statuses=new Set(["pending","in_progress","completed","cancelled"])
6
+ const priorities=new Set(["high","medium","low"])
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
+ 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
+ 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
+ }
35
+ var todos={type:"array",items:item,maxItems:100}
36
+ var sessionID={type:"string",minLength:1}
37
+ var output={type:"object",properties:{todos,guidance:{type:"boolean"}},required:["todos","guidance"],additionalProperties:false
38
+ }
39
+ var NavigatorTodoRpc={id:"opencode-navigator.todo",methods:{list:{input:{type:"object",properties:{sessionID},required:["sessionID"],additionalProperties:false},output
40
+ },replace:{input:{type:"object",properties:{sessionID,todos},required:["sessionID","todos"],additionalProperties:false
41
+ },output
42
+ },guidance:{input:{type:"object",properties:{sessionID,enabled:{type:"boolean"}},required:["sessionID","enabled"],additionalProperties:false
43
+ },output
44
+ }},events:{updated:{schema:{type:"object",properties:{sessionID,todos,guidance:{type:"boolean"}},required:["sessionID","todos","guidance"],additionalProperties:false
45
+ }}}};
46
+ var input={type:"object",properties:{todos:NavigatorTodoRpc.methods.replace.input.properties.todos
47
+ },required:["todos"],additionalProperties:false
48
+ }
49
+ var noNotify=async ()=>{}
50
+ var todo_server_default=Plugin.define({id:"opencode-navigator.todo-server",async setup(host){let notify=noNotify
51
+ const todos=createManagedTodos({get:(key)=>host.storage.get(key),set:(key,value)=>host.storage.set(key,value)
52
+ },(sessionID,state)=>notify(sessionID,state))
53
+ const rpc=await host.rpc.register(NavigatorTodoRpc,{list:async (value)=>todos.list(value.sessionID),replace:async (value)=>{const{sessionID,todos:items}=value
54
+ return todos.replace(sessionID,items)
55
+ },guidance:async (value)=>{const{sessionID,enabled}=value
56
+ return todos.guidance(sessionID,enabled)
57
+ }})
58
+ notify=(sessionID,state)=>rpc.events.emit("updated",{sessionID,...state})
59
+ const tools=await host.tool.transform((editor)=>{editor.namespace({name:"navigator",description:"Navigator session tasks"})
60
+ 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)
61
+ return{content:`Navigator Todo updated: ${state.todos.length} tasks.`}}})
62
+ })
63
+ const context=await host.session.hook("context",async (event)=>{const state=await todos.list(event.sessionID)
64
+ if(state.guidance)
65
+ event.system.push({type:"text",text:todoGuidance(state)})
66
+ })
67
+ return async ()=>{await context.dispose()
68
+ await tools.dispose()
69
+ await rpc.dispose()
70
+ }}})
71
+ export{todo_server_default as default
72
+ }
package/dist/tui.js CHANGED
@@ -2,7 +2,7 @@ import {insert as _$insert,setProp as _$setProp,createElement as _$createElement
2
2
 
3
3
 
4
4
 
5
- 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,Show,splitProps as splitProps3,createSignal as createSignal4,createSignal as createSignal5,createSignal as createSignal6,onCleanup as onCleanup3,batch,createSignal as createSignal8,createSignal as createSignal7,createEffect as createEffect4,createSignal as createSignal9,onCleanup as onCleanup4,createSignal as createSignal10,Index,createSignal as createSignal11,onCleanup as onCleanup5,createEffect as createEffect6,onCleanup as onCleanup7,batch as batch2,createEffect as createEffect5,createMemo,createSignal as createSignal12,onCleanup as onCleanup6,onMount,For as For2,For as For3,Show as Show2,createMemo as createMemo2,Show as Show3,createSignal as createSignal13,onCleanup as onCleanup8,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 Show5,createEffect as createEffect7,createSignal as createSignal14,onCleanup as onCleanup9,Show as Show4,For as For6,For as For9,createEffect as createEffect8,createMemo as createMemo5,onCleanup as onCleanup10,For as For10,createEffect as createEffect9,createMemo as createMemo8,onCleanup as onCleanup11,onMount as onMount4,createMemo as createMemo6,createMemo as createMemo7,Show as Show6,onCleanup as onCleanup12,createSignal as createSignal15,createEffect as createEffect10,onCleanup as onCleanup13,onMount as onMount5,onCleanup as onCleanup14,onMount as onMount6,For as For11,createSignal as createSignal16,createSignal as createSignal17,Show as Show7,createSignal as createSignal19,createSignal as createSignal18,onCleanup as onCleanup15,For as For12,Show as Show10,createMemo as createMemo10,createComputed,createMemo as createMemo9,createSignal as createSignal20,untrack as untrack2,Show as Show8,createComputed as createComputed2,untrack as untrack3,Show as Show9,createEffect as createEffect11,createSignal as createSignal21,createMemo as createMemo12,createSignal as createSignal23,createMemo as createMemo11,For as For13,Show as Show13,Show as Show11,Show as Show12,createSignal as createSignal22,onCleanup as onCleanup16,For as For14,Show as Show15,createMemo as createMemo14,Show as Show14,createMemo as createMemo13,createEffect as createEffect12,onCleanup as onCleanup17,For as For19,Match,Switch,createMemo as createMemo18,onCleanup as onCleanup21,For as For15,Show as Show16,createSignal as createSignal24,createEffect as createEffect13,createMemo as createMemo15,onCleanup as onCleanup18,untrack as untrack4,For as For17,Show as Show19,createEffect as createEffect14,createMemo as createMemo16,createSignal as createSignal25,onCleanup as onCleanup19,untrack as untrack5,For as For16,Show as Show18,Show as Show17,Show as Show22,createEffect as createEffect15,createMemo as createMemo17,createSignal as createSignal26,onCleanup as onCleanup20,untrack as untrack6,For as For18,Show as Show21,Show as Show20,createEffect as createEffect16,createMemo as createMemo19,onCleanup as onCleanup22,untrack as untrack7,Show as Show23,createMemo as createMemo20,onCleanup as onCleanup23,createSignal as createSignal27,onCleanup as onCleanup24,onMount as onMount7} from "solid-js"
5
+ 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,Show,splitProps as splitProps3,createSignal as createSignal4,createSignal as createSignal5,createSignal as createSignal6,onCleanup as onCleanup3,batch,createSignal as createSignal8,createSignal as createSignal7,createEffect as createEffect4,createSignal as createSignal9,onCleanup as onCleanup4,createSignal as createSignal10,createSignal as createSignal11,Index,createSignal as createSignal12,onCleanup as onCleanup5,createEffect as createEffect6,onCleanup as onCleanup7,batch as batch2,createEffect as createEffect5,createMemo,createSignal as createSignal13,onCleanup as onCleanup6,onMount,For as For2,For as For3,Show as Show2,createMemo as createMemo2,Show as Show3,createSignal as createSignal14,onCleanup as onCleanup8,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 Show5,createEffect as createEffect7,createSignal as createSignal15,onCleanup as onCleanup9,Show as Show4,For as For6,For as For9,createEffect as createEffect8,createMemo as createMemo5,onCleanup as onCleanup10,For as For10,createEffect as createEffect9,createMemo as createMemo8,onCleanup as onCleanup11,onMount as onMount4,createMemo as createMemo6,createMemo as createMemo7,Show as Show6,onCleanup as onCleanup12,createSignal as createSignal16,createEffect as createEffect10,onCleanup as onCleanup13,onMount as onMount5,onCleanup as onCleanup14,onMount as onMount6,For as For11,createSignal as createSignal17,createSignal as createSignal18,Show as Show7,createSignal as createSignal20,createSignal as createSignal19,onCleanup as onCleanup15,For as For12,Show as Show10,createMemo as createMemo10,createComputed,createMemo as createMemo9,createSignal as createSignal21,untrack as untrack2,Show as Show8,createComputed as createComputed2,untrack as untrack3,Show as Show9,createEffect as createEffect11,createSignal as createSignal22,createMemo as createMemo12,createSignal as createSignal24,createMemo as createMemo11,For as For13,Show as Show13,Show as Show11,Show as Show12,createSignal as createSignal23,onCleanup as onCleanup16,For as For14,Show as Show15,createMemo as createMemo14,Show as Show14,createMemo as createMemo13,createSignal as createSignal25,createEffect as createEffect12,onCleanup as onCleanup17,For as For19,Match,Switch,createMemo as createMemo18,onCleanup as onCleanup21,For as For15,Show as Show16,createSignal as createSignal26,createEffect as createEffect13,createMemo as createMemo15,onCleanup as onCleanup18,untrack as untrack4,For as For17,Show as Show19,createEffect as createEffect14,createMemo as createMemo16,createSignal as createSignal27,onCleanup as onCleanup19,untrack as untrack5,For as For16,Show as Show18,Show as Show17,Show as Show23,createEffect as createEffect15,createMemo as createMemo17,createSignal as createSignal28,onCleanup as onCleanup20,untrack as untrack6,For as For18,Show as Show21,Show as Show20,Show as Show22,createEffect as createEffect16,createMemo as createMemo19,onCleanup as onCleanup22,untrack as untrack7,Show as Show24,createMemo as createMemo20,onCleanup as onCleanup23,createSignal as createSignal29,onCleanup as onCleanup24,onMount as onMount7} from "solid-js"
6
6
  var SIDEBAR_SECTIONS=["todo","subagents","skills","quick_actions","lsp","mcp"]
7
7
  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"]
8
8
  function preferencesScope(path){return path.worktree||path.directory||"global"
@@ -2619,6 +2619,107 @@ activeTarget=undefined
2619
2619
  requests.abortAll()
2620
2620
  refreshing.clear()
2621
2621
  }}}}
2622
+ function normalizeTodoState(value){if(!value||typeof value!=="object")
2623
+ return{todos:[],guidance:false}
2624
+ const state=value
2625
+ const statuses=new Set(["pending","in_progress","completed","cancelled"])
2626
+ const priorities=new Set(["high","medium","low"])
2627
+ 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}})):[]
2628
+ return{todos,guidance:state.guidance===true}}
2629
+
2630
+ 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
2631
+ }
2632
+ var todos={type:"array",items:item,maxItems:100}
2633
+ var sessionID={type:"string",minLength:1}
2634
+ var output={type:"object",properties:{todos,guidance:{type:"boolean"}},required:["todos","guidance"],additionalProperties:false
2635
+ }
2636
+ var NavigatorTodoRpc={id:"opencode-navigator.todo",methods:{list:{input:{type:"object",properties:{sessionID},required:["sessionID"],additionalProperties:false},output
2637
+ },replace:{input:{type:"object",properties:{sessionID,todos},required:["sessionID","todos"],additionalProperties:false
2638
+ },output
2639
+ },guidance:{input:{type:"object",properties:{sessionID,enabled:{type:"boolean"}},required:["sessionID","enabled"],additionalProperties:false
2640
+ },output
2641
+ }},events:{updated:{schema:{type:"object",properties:{sessionID,todos,guidance:{type:"boolean"}},required:["sessionID","todos","guidance"],additionalProperties:false
2642
+ }}}};
2643
+ function isMissingServer(error){return typeof error==="object"&&error!==null&&"type" in error&&error.type==="rpc.unavailable"
2644
+ }
2645
+ function createNavigatorTodoController(context,api){const rpc=context.client.rpc(NavigatorTodoRpc)
2646
+ const [sessions,setSessions]=createSignal11({})
2647
+ const [saving,setSaving]=createSignal11({})
2648
+ const [errors,setErrors]=createSignal11({})
2649
+ const [missing,setMissing]=createSignal11({})
2650
+ const requests=createRequestState(api.lifecycle.signal)
2651
+ const refreshing=new Map
2652
+ const revisions=new Map
2653
+ let activeTarget
2654
+ function target(sessionID){const session=api.state.session.get(sessionID)
2655
+ const directory=session?.directory??api.state.path.directory
2656
+ const workspace=session?.workspaceID
2657
+ return{key:JSON.stringify([sessionID,directory,workspace??null]),sessionID,routing:{directory}}}
2658
+ function set(key,value){setSessions((current)=>({...current,[key]:normalizeTodoState(value)}))
2659
+ }
2660
+ function refresh(sessionID,force=false){const current=target(sessionID)
2661
+ const pending=refreshing.get(current.key)
2662
+ if(pending&&!force)
2663
+ return pending
2664
+ const revision=revisions.get(current.key)??0
2665
+ const requestState=requests.start(current.key,"refresh Navigator todos",current.key in sessions(),force)
2666
+ if(!requestState)
2667
+ return pending
2668
+ const request=rpc.list({sessionID},{location:current.routing,signal:requestState.signal}).then((value)=>{const state=normalizeTodoState(value)
2669
+ if(requestState.isCurrent()){setMissing((values)=>({...values,[current.key]:false}))
2670
+ if((revisions.get(current.key)??0)===revision)
2671
+ set(current.key,state)
2672
+ }
2673
+ requestState.succeed()
2674
+ return state
2675
+ }).catch((error)=>{if(requestState.isCurrent()&&!requestState.signal.aborted)
2676
+ setMissing((values)=>({...values,[current.key]:isMissingServer(error)}))
2677
+ requestState.fail(error)
2678
+ throw error
2679
+ }).finally(()=>{requestState.finish()
2680
+ if(refreshing.get(current.key)===request)
2681
+ refreshing.delete(current.key)
2682
+ })
2683
+ refreshing.set(current.key,request)
2684
+ return request
2685
+ }
2686
+ const unsubscribe=rpc.events.on("updated",(event)=>{const{sessionID}=event.data
2687
+ if(typeof sessionID!=="string")
2688
+ return
2689
+ const current=target(sessionID)
2690
+ if(event.location.directory!==current.routing.directory)
2691
+ return
2692
+ revisions.set(current.key,(revisions.get(current.key)??0) + 1)
2693
+ set(current.key,normalizeTodoState(event.data))
2694
+ })
2695
+ api.lifecycle.onDispose(unsubscribe)
2696
+ return{target,list(sessionID){return sessions()[target(sessionID).key]?.todos??[]
2697
+ },refresh,state(sessionID){return requests.state(target(sessionID).key)
2698
+ },missing(sessionID){return missing()[target(sessionID).key]??false
2699
+ },retry(sessionID){return refresh(sessionID,true)
2700
+ },guidance(sessionID){const key=target(sessionID).key
2701
+ return{enabled:sessions()[key]?.guidance??false,saving:saving()[key]??false,error:errors()[key],available:key in sessions()
2702
+ }},async setGuidance(sessionID,enabled){const current=target(sessionID)
2703
+ if(!(current.key in sessions())||saving()[current.key])
2704
+ return
2705
+ setSaving((value)=>({...value,[current.key]:true}))
2706
+ setErrors((value)=>({...value,[current.key]:undefined}))
2707
+ try{const state=await rpc.guidance({sessionID,enabled},{location:current.routing})
2708
+ revisions.set(current.key,(revisions.get(current.key)??0) + 1)
2709
+ set(current.key,normalizeTodoState(state))
2710
+ } catch(error){setErrors((value)=>({...value,[current.key]:error instanceof Error?error.message:String(error)}))
2711
+ } finally{setSaving((value)=>({...value,[current.key]:false}))
2712
+ }},activate(sessionID){const key=target(sessionID).key
2713
+ if(activeTarget!==key){requests.abortAll()
2714
+ refreshing.clear()
2715
+ activeTarget=key
2716
+ }
2717
+ return()=>{if(activeTarget!==key)
2718
+ return
2719
+ activeTarget=undefined
2720
+ requests.abortAll()
2721
+ refreshing.clear()
2722
+ }}}}
2622
2723
  function buildTodoView(todos,mode){const active=[...todos.filter((item)=>item.status==="in_progress"),...todos.filter((item)=>item.status==="pending")]
2623
2724
  const completed=todos.filter((item)=>item.status==="completed")
2624
2725
  const cancelled=todos.filter((item)=>item.status==="cancelled")
@@ -2673,7 +2774,7 @@ return action.label
2673
2774
  const command=api.keymap.getCommands?.({visibility:"registered"}).find((candidate)=>candidate.name===action.command)
2674
2775
  return typeof command?.title==="string"?command.title:action.label
2675
2776
  }
2676
- function createQuickActionRevision(api){const [revision,setRevision]=createSignal11(0)
2777
+ function createQuickActionRevision(api){const [revision,setRevision]=createSignal12(0)
2677
2778
  const unsubscribe=api.keymap.on?.("state",()=>setRevision((value)=>value + 1))
2678
2779
  if(unsubscribe)
2679
2780
  onCleanup5(unsubscribe)
@@ -2771,7 +2872,7 @@ const [selectedID,setSelectedID]=useDialogState("selection",undefined)
2771
2872
  const [views]=useDialogState("tab-views",new Map)
2772
2873
  const tabViews=views()
2773
2874
  const [scrollTop,setScrollTop]=useDialogState("scroll",0)
2774
- const [refreshing,setRefreshing]=createSignal12(false)
2875
+ const [refreshing,setRefreshing]=createSignal13(false)
2775
2876
  const theme=()=>props.api.theme.current
2776
2877
  const icons=useIcons()
2777
2878
  const commandRevision=createQuickActionRevision(props.api)
@@ -3397,8 +3498,8 @@ return _el$
3397
3498
  }
3398
3499
  function FirstRunWizard(props){const icons=useIcons()
3399
3500
  const dialogs=useDialogs(props.api)
3400
- const [active,setActive]=createSignal13(0)
3401
- const [step,setStep]=createSignal13(0)
3501
+ const [active,setActive]=createSignal14(0)
3502
+ const [step,setStep]=createSignal14(0)
3402
3503
  const theme=()=>props.api.theme.current
3403
3504
  const sections=SECTION_DEFINITIONS.filter((section)=>supportsSidebarSection(props.api,section.name))
3404
3505
  const finishIndex=sections.length
@@ -3586,7 +3687,7 @@ const dialogs=useDialogs(props.api)
3586
3687
  const theme=()=>props.api.theme.current
3587
3688
  const dimensions=useTerminalDimensions4()
3588
3689
  const scroll=useDialogScroll()
3589
- const [active,setActive]=createSignal14("apply")
3690
+ const [active,setActive]=createSignal15("apply")
3590
3691
  let body
3591
3692
  const prefix=`${PLUGIN_ID}.preset-preview`
3592
3693
  function activate(action){if(!props.valid()){dialogs.close()
@@ -4628,7 +4729,7 @@ return _$createElement("box")
4628
4729
 
4629
4730
 
4630
4731
  function SettingsFooterButton(props){const icons=useIcons()
4631
- const [hovered,setHovered]=createSignal15(false)
4732
+ const [hovered,setHovered]=createSignal16(false)
4632
4733
  const theme=()=>props.api.theme.current
4633
4734
  return _$createComponent(IconControl,{id:`${PLUGIN_ID}.settings.footer`,get icon(){return icons.icon("settings")
4634
4735
  },get foregroundColor(){return _$memo(()=>!!hovered())()?theme().text:theme().textMuted
@@ -4849,8 +4950,8 @@ return _$createElement("box")
4849
4950
 
4850
4951
  var NAVIGATOR_PACKAGE_URL="https://registry.npmjs.org/opencode-navigator/latest"
4851
4952
  var REQUEST_TIMEOUT_MS=5000
4852
- function createVersionStatus(api,navigatorVersion,request=fetch){const [openCodeUpdate,setOpenCodeUpdate]=createSignal16()
4853
- const [navigatorUpdate,setNavigatorUpdate]=createSignal16()
4953
+ function createVersionStatus(api,navigatorVersion,request=fetch){const [openCodeUpdate,setOpenCodeUpdate]=createSignal17()
4954
+ const [navigatorUpdate,setNavigatorUpdate]=createSignal17()
4854
4955
  const controller=new AbortController
4855
4956
  const timeout=setTimeout(()=>controller.abort(),REQUEST_TIMEOUT_MS)
4856
4957
  const unsubscribe=api.event.on("installation.update-available",(event)=>{const version=event.properties.version
@@ -5013,9 +5114,9 @@ return false
5013
5114
  return true
5014
5115
  }
5015
5116
  function createSidebarInteraction(api,showHelp=()=>openKeyboardHelp(api)){const descriptors=new Map
5016
- const [selectedId,setSelectedId]=createSignal17()
5017
- const [focusWithin,setFocusWithin]=createSignal17(false)
5018
- const [revision,setRevision]=createSignal17(0)
5117
+ const [selectedId,setSelectedId]=createSignal18()
5118
+ const [focusWithin,setFocusWithin]=createSignal18(false)
5119
+ const [revision,setRevision]=createSignal18(0)
5019
5120
  let titleRoot
5020
5121
  let contentRoot
5021
5122
  let targetedRoot
@@ -5284,7 +5385,7 @@ else if(highlighted)
5284
5385
  foregroundColor=theme.selectedListItemText
5285
5386
  return{backgroundColor,foregroundColor}}
5286
5387
  function useSidebarItem(api,interaction,descriptor,normalColor=()=>api.theme.current.text,mode="row"){let unregister
5287
- const [hovered,setHovered]=createSignal18(false)
5388
+ const [hovered,setHovered]=createSignal19(false)
5288
5389
  const disabled=()=>descriptor.disabled?.()===true
5289
5390
  const focused=()=>Boolean(interaction?.ownsFocus()&&interaction.isSelected(descriptor.id))
5290
5391
  const colors=()=>sidebarInteractiveColors(api.theme.current,{focused:focused(),hovered:hovered(),disabled:disabled()},normalColor(),mode)
@@ -5302,7 +5403,7 @@ return true
5302
5403
  }}}
5303
5404
  function LspBadge(props){const icons=useIcons()
5304
5405
  const isKnown=lspIconName(props.id)!==undefined
5305
- const [showName,setShowName]=createSignal19(false)
5406
+ const [showName,setShowName]=createSignal20(false)
5306
5407
  const statusColor=()=>props.status==="connected"?props.api.theme.current.success:props.api.theme.current.error
5307
5408
  if(!isKnown){return(()=>{var _el$=_$createElement("text")
5308
5409
  _$setProp(_el$,"flexShrink",1)
@@ -5345,7 +5446,7 @@ return _el$3
5345
5446
 
5346
5447
 
5347
5448
 
5348
- function createListVisibility(options){const [expanded,setExpanded]=createSignal20(false)
5449
+ function createListVisibility(options){const [expanded,setExpanded]=createSignal21(false)
5349
5450
  let previousKey
5350
5451
  let previousLimit
5351
5452
  const canToggle=createMemo9(()=>options.limit()>0&&options.items().length>options.limit())
@@ -5427,7 +5528,7 @@ result += segment
5427
5528
  return result + ellipsis
5428
5529
  }
5429
5530
  function Section(props){const icons=useIcons()
5430
- const [actionWidth,setActionWidth]=createSignal21()
5531
+ const [actionWidth,setActionWidth]=createSignal22()
5431
5532
  const icon=()=>props.section?icons.section(props.section):props.icon
5432
5533
  const theme=()=>props.api.theme.current
5433
5534
  const item=useSidebarItem(props.api,props.interaction,{id:props.sectionId??`section.${props.title}`,position:()=>({section:props.navigationSection??0,row:0,column:0
@@ -5778,7 +5879,7 @@ return _el$2
5778
5879
  function SectionFilter(props){const icons=useIcons()
5779
5880
  let input
5780
5881
  let unregisterInput
5781
- const [inputFocused,setInputFocused]=createSignal22(false)
5882
+ const [inputFocused,setInputFocused]=createSignal23(false)
5782
5883
  const theme=()=>props.api.theme.current
5783
5884
  const item=useSidebarItem(props.api,props.interaction,{id:props.id??`filter.${props.placeholder}`,position:()=>props.position??{section:0,row:0,column:0
5784
5885
  },activate:()=>{if(!input)
@@ -5953,7 +6054,7 @@ return _el$
5953
6054
  }})
5954
6055
  }
5955
6056
  function McpSection(props){const icons=useIcons()
5956
- const [query,setQuery]=createSignal23("")
6057
+ const [query,setQuery]=createSignal24("")
5957
6058
  const model=createMcpSectionModel(props,query)
5958
6059
  const navigationSection=()=>props.navigationSection??6
5959
6060
  const summary=createMemo12(()=>`${model.active()}/${model.list().length}${model.errors() ? ` \xB7 ${model.errors()} ${icons.icon("error")}` : ""}`)
@@ -5990,10 +6091,14 @@ return _$createComponent(SectionWithHeaderAction,{get api(){return props.api
5990
6091
 
5991
6092
  function QuickActionRow(props){const icons=useIcons()
5992
6093
  const theme=()=>props.api.theme.current
6094
+ const [width,setWidth]=createSignal25(0)
5993
6095
  const shortcut=createMemo13(()=>{const bindings=props.api.keymap.getCommandBindings({visibility:"registered",commands:[props.action.command]
5994
6096
  })
5995
6097
  return props.api.keys.formatBindings(bindings.get(props.action.command))
5996
6098
  })
6099
+ const visibleShortcut=createMemo13(()=>{const value=shortcut()
6100
+ return value&&width()>=Bun.stringWidth(props.action.label) + Bun.stringWidth(value) + 9?value:undefined
6101
+ })
5997
6102
  function run(){if(props.action.command==="session.new"&&props.onNewSession?.())
5998
6103
  return
5999
6104
  const result=props.interaction?props.interaction.dispatchFromReturnTarget(props.action.command):props.api.keymap.dispatchCommand(props.action.command)
@@ -6014,6 +6119,7 @@ return background==="transparent"?row.backgroundColor():background
6014
6119
  }
6015
6120
  return _$createComponent(ListRow,{ref:(node)=>row.ref(node),get id(){return id()
6016
6121
  },paddingLeft:1,paddingRight:1,get marginTop(){return props.separator?1:0
6122
+ },onSizeChange:function(){setWidth(this.width)
6017
6123
  },get backgroundColor(){return row.backgroundColor()
6018
6124
  },get onMouseOver(){return row.onMouseOver
6019
6125
  },get onMouseOut(){return row.onMouseOut
@@ -6027,14 +6133,11 @@ return _el$
6027
6133
  _$setProp(_el$2,"flexGrow",1)
6028
6134
  _$setProp(_el$2,"wrapMode","none")
6029
6135
  _$setProp(_el$2,"truncate",true)
6030
- _$insert(_el$2,()=>props.action.label,null)
6031
- _$insert(_el$2,(()=>{var _c$=_$memo(()=>!!props.disabled)
6032
- return()=>_c$()?` \xB7 ${props.disabled}`:""
6033
- })(),null)
6136
+ _$insert(_el$2,()=>props.action.label)
6034
6137
  _$effect((_$p)=>_$setProp(_el$2,"fg",row.foregroundColor(),_$p))
6035
6138
  return _el$2
6036
6139
  })()
6037
- },get metadata(){return _$createComponent(Show14,{get when(){return shortcut()
6140
+ },get metadata(){return _$createComponent(Show14,{get when(){return visibleShortcut()
6038
6141
  },children:(value)=>(()=>{var _el$3=_$createElement("text")
6039
6142
  _$setProp(_el$3,"flexShrink",0)
6040
6143
  _$setProp(_el$3,"wrapMode","none")
@@ -6043,6 +6146,14 @@ _$effect((_$p)=>_$setProp(_el$3,"fg",row.focused()?row.foregroundColor():theme()
6043
6146
  return _el$3
6044
6147
  })()
6045
6148
  })
6149
+ },get details(){return _$createComponent(Show14,{get when(){return props.disabled
6150
+ },children:(reason)=>(()=>{var _el$4=_$createElement("text")
6151
+ _$setProp(_el$4,"wrapMode","word")
6152
+ _$insert(_el$4,reason)
6153
+ _$effect((_$p)=>_$setProp(_el$4,"fg",row.focused()?row.foregroundColor():theme().textMuted,_$p))
6154
+ return _el$4
6155
+ })()
6156
+ })
6046
6157
  },get trailing(){return _$createComponent(BookmarkControl,{ref:(node)=>favorite.ref(node),get id(){return `${id()}.favorite`
6047
6158
  },get bookmarked(){return props.favorite
6048
6159
  },get backgroundColor(){return controlBackground()
@@ -6299,7 +6410,7 @@ return _el$2
6299
6410
  }})
6300
6411
  }
6301
6412
  function SkillsSection(props){const dialogs=useDialogs(props.api)
6302
- const [query,setQuery]=createSignal24("")
6413
+ const [query,setQuery]=createSignal26("")
6303
6414
  const model=createSkillSectionModel(props,query)
6304
6415
  const navigationSection=()=>props.navigationSection??3
6305
6416
  function selectSkill(item){if(!props.preferences.shouldConfirmSkill(item)){model.useSkill(item)
@@ -6374,8 +6485,8 @@ return _el$
6374
6485
  function createSubagentSectionModel(props){const list=createMemo16(()=>props.controller.list(props.sessionID))
6375
6486
  const recent=createMemo16(()=>props.controller.recent?.(props.sessionID)??[])
6376
6487
  const rows=createMemo16(()=>buildSubagentView(list(),recent()))
6377
- const [query,setQuery]=createSignal25("")
6378
- const [mode,setMode]=createSignal25("all")
6488
+ const [query,setQuery]=createSignal27("")
6489
+ const [mode,setMode]=createSignal27("all")
6379
6490
  const counts=createMemo16(()=>countSubagentView(rows(),query()))
6380
6491
  const tabs=createMemo16(()=>["all","active","recent",...counts().errors>0?["errors"]:[]])
6381
6492
  const filtered=createMemo16(()=>filterSubagentView(rows(),mode(),query()))
@@ -6657,6 +6768,7 @@ return _el$
6657
6768
  })()
6658
6769
  }
6659
6770
  function TodoContents(props){const navigationSection=()=>props.navigationSection??1
6771
+ const tabsRow=()=>props.managed?3:2
6660
6772
  return(()=>{var _el$=_$createElement("box"),_el$2=_$createElement("box")
6661
6773
  _$insertNode(_el$,_el$2)
6662
6774
  _$setProp(_el$,"gap",1)
@@ -6664,7 +6776,7 @@ _$setProp(_el$2,"flexDirection","row")
6664
6776
  _$setProp(_el$2,"flexWrap","wrap")
6665
6777
  _$insert(_el$2,_$createComponent(For18,{each:["all","active","finished"],children:(value,index)=>_$createComponent(SectionTab,{get api(){return props.api
6666
6778
  },get interaction(){return props.interaction
6667
- },id:`${PLUGIN_ID}.todo.filter.${value}`,get position(){return{section:navigationSection(),row:2,column:index()
6779
+ },id:`${PLUGIN_ID}.todo.filter.${value}`,get position(){return{section:navigationSection(),row:tabsRow(),column:index()
6668
6780
  }},get label(){return value[0].toUpperCase() + value.slice(1)
6669
6781
  },get count(){return props.view.counts[value]
6670
6782
  },get selected(){return props.mode===value
@@ -6698,7 +6810,8 @@ return _el$3
6698
6810
  _$insert(_el$,_$createComponent(ListVisibilityControl,{get api(){return props.api
6699
6811
  },get interaction(){return props.interaction
6700
6812
  },section:"todo",get navigationSection(){return navigationSection()
6701
- },row:3,get visibility(){return props.visibility
6813
+ },get row(){return tabsRow() + 1
6814
+ },get visibility(){return props.visibility
6702
6815
  }}),null)
6703
6816
  return _el$
6704
6817
  })()
@@ -6709,10 +6822,39 @@ _$effect((_$p)=>_$setProp(_el$6,"fg",props.api.theme.current.textMuted,_$p))
6709
6822
  return _el$6
6710
6823
  })()
6711
6824
  }
6825
+
6826
+
6827
+
6828
+
6829
+
6830
+
6831
+ function TodoGuidanceControl(props){const state=()=>props.controller.guidance(props.sessionID)
6832
+ return _$createComponent(Show22,{get when(){return state().available
6833
+ },get children(){var _el$=_$createElement("box")
6834
+ _$insert(_el$,_$createComponent(SectionTab,{get api(){return props.api
6835
+ },get interaction(){return props.interaction
6836
+ },id:`${PLUGIN_ID}.todo.guidance`,get position(){return{section:props.navigationSection,row:2,column:0
6837
+ }},get label(){return `Todo instructions: ${state().enabled ? "On" : "Off"}`
6838
+ },get selected(){return state().enabled
6839
+ },get disabled(){return state().saving
6840
+ },onActivate:()=>void props.controller.setGuidance(props.sessionID,!state().enabled)
6841
+ }),null)
6842
+ _$insert(_el$,_$createComponent(Show22,{get when(){return state().error
6843
+ },children:(message)=>(()=>{var _el$2=_$createElement("text")
6844
+ _$setProp(_el$2,"wrapMode","word")
6845
+ _$insert(_el$2,message)
6846
+ _$effect((_$p)=>_$setProp(_el$2,"fg",props.api.theme.current.error,_$p))
6847
+ return _el$2
6848
+ })()
6849
+ }),null)
6850
+ return _el$
6851
+ }})
6852
+ }
6712
6853
  function TodoSection(props){const list=createMemo17(()=>props.controller.list(props.sessionID))
6713
6854
  const icons=useIcons()
6714
6855
  const unavailable=()=>hostCapabilityUnavailable(props.api,"todo")
6715
- const [mode,setMode]=createSignal26("all")
6856
+ const managed=()=>("guidance" in props.controller)?props.controller:undefined
6857
+ const [mode,setMode]=createSignal28("all")
6716
6858
  const targetKey=createMemo17(()=>props.controller.target?.(props.sessionID).key??props.sessionID)
6717
6859
  createEffect15(()=>{targetKey()
6718
6860
  setMode("all")
@@ -6733,30 +6875,51 @@ const navigationSection=()=>props.navigationSection??1
6733
6875
  return _$createComponent(Section,{get api(){return props.api
6734
6876
  },get interaction(){return props.interaction
6735
6877
  },sectionId:`${PLUGIN_ID}.section.todo`,get navigationSection(){return navigationSection()
6736
- },title:"TODO",section:"todo",get summary(){return _$memo(()=>!!unavailable())()?"\u2014":`${view().counts.completed}/${list().length}`
6878
+ },title:"TODO",section:"todo",get summary(){return _$memo(()=>!!(unavailable()||managed()?.missing(props.sessionID)))()?"\u2014":`${view().counts.completed}/${list().length}`
6737
6879
  },get open(){return props.preferences.expanded().todo
6738
- },onToggle:()=>props.preferences.toggleSectionExpanded("todo"),get children(){return _$createComponent(Show22,{get when(){return unavailable()
6880
+ },onToggle:()=>props.preferences.toggleSectionExpanded("todo"),get children(){return _$createComponent(Show23,{get when(){return unavailable()
6881
+ },get fallback(){return(()=>{var _el$=_$createElement("box")
6882
+ _$setProp(_el$,"gap",1)
6883
+ _$insert(_el$,_$createComponent(Show23,{get when(){return managed()?.guidance(props.sessionID).available
6884
+ },get children(){return _$createComponent(TodoGuidanceControl,{get api(){return props.api
6885
+ },get controller(){return managed()
6886
+ },get sessionID(){return props.sessionID
6887
+ },get interaction(){return props.interaction
6888
+ },get navigationSection(){return navigationSection()
6889
+ }})
6890
+ }}),null)
6891
+ _$insert(_el$,_$createComponent(Show23,{get when(){return managed()?.missing(props.sessionID)
6739
6892
  },get fallback(){return _$createComponent(SectionRequestBody,{get api(){return props.api
6740
6893
  },get interaction(){return props.interaction
6741
6894
  },id:`${PLUGIN_ID}.retry.todo`,get position(){return{section:navigationSection(),row:1,column:0
6742
6895
  }},get state(){return state()
6743
6896
  },get hasItems(){return list().length>0
6744
- },empty:"No tasks yet",loading:"Loading tasks\u2026",onRetry:()=>void props.controller.retry(props.sessionID),get children(){return _$createComponent(TodoContents,{get api(){return props.api
6897
+ },empty:"No tasks yet",loading:"Loading tasks\u2026",onRetry:()=>void props.controller.retry(props.sessionID).catch(()=>{}),get children(){return _$createComponent(TodoContents,{get api(){return props.api
6745
6898
  },get interaction(){return props.interaction
6746
6899
  },get navigationSection(){return props.navigationSection
6747
6900
  },get mode(){return mode()
6748
6901
  },setMode,get view(){return view()
6749
6902
  },visibility,get density(){return props.preferences.rowDensity?.()??"compact"
6903
+ },get managed(){return Boolean(managed())
6750
6904
  }})
6751
6905
  }})
6752
- },children:(message)=>(()=>{var _el$=_$createElement("text"),_el$2=_$createTextNode(` `)
6753
- _$insertNode(_el$,_el$2)
6754
- _$setProp(_el$,"wrapMode","word")
6755
- _$insert(_el$,()=>icons.icon("info"),_el$2)
6756
- _$insert(_el$,message,null)
6757
- _$effect((_$p)=>_$setProp(_el$,"fg",props.api.theme.current.textMuted,_$p))
6906
+ },get children(){var _el$2=_$createElement("text"),_el$3=_$createTextNode(` Navigator Todo needs a separate OpenCode 2 server plugin. See the README setup steps and restart OpenCode.`)
6907
+ _$insertNode(_el$2,_el$3)
6908
+ _$setProp(_el$2,"wrapMode","word")
6909
+ _$insert(_el$2,()=>icons.icon("info"),_el$3)
6910
+ _$effect((_$p)=>_$setProp(_el$2,"fg",props.api.theme.current.textMuted,_$p))
6911
+ return _el$2
6912
+ }}),null)
6758
6913
  return _el$
6759
6914
  })()
6915
+ },children:(message)=>(()=>{var _el$4=_$createElement("text"),_el$5=_$createTextNode(` `)
6916
+ _$insertNode(_el$4,_el$5)
6917
+ _$setProp(_el$4,"wrapMode","word")
6918
+ _$insert(_el$4,()=>icons.icon("info"),_el$5)
6919
+ _$insert(_el$4,message,null)
6920
+ _$effect((_$p)=>_$setProp(_el$4,"fg",props.api.theme.current.textMuted,_$p))
6921
+ return _el$4
6922
+ })()
6760
6923
  })
6761
6924
  }})
6762
6925
  }
@@ -6868,7 +7031,7 @@ _$setProp(_el$2,"flexDirection","row")
6868
7031
  _$setProp(_el$2,"justifyContent","space-between")
6869
7032
  _$setProp(_el$2,"gap",1)
6870
7033
  _$setProp(_el$3,"flexGrow",1)
6871
- _$insert(_el$3,_$createComponent(Show23,{get when(){return props.preferences.showSessionTitle()
7034
+ _$insert(_el$3,_$createComponent(Show24,{get when(){return props.preferences.showSessionTitle()
6872
7035
  },get children(){var _el$4=_$createElement("text"),_el$5=_$createElement("b")
6873
7036
  _$insertNode(_el$4,_el$5)
6874
7037
  _$setProp(_el$4,"wrapMode","word")
@@ -6876,7 +7039,7 @@ _$insert(_el$5,()=>props.title)
6876
7039
  _$effect((_$p)=>_$setProp(_el$4,"fg",theme().text,_$p))
6877
7040
  return _el$4
6878
7041
  }}),null)
6879
- _$insert(_el$3,_$createComponent(Show23,{get when(){return _$memo(()=>!!props.preferences.showSessionDate())()&&created()
7042
+ _$insert(_el$3,_$createComponent(Show24,{get when(){return _$memo(()=>!!props.preferences.showSessionDate())()&&created()
6880
7043
  },children:(value)=>(()=>{var _el$6=_$createElement("text")
6881
7044
  _$insert(_el$6,value)
6882
7045
  _$effect((_$p)=>_$setProp(_el$6,"fg",theme().textMuted,_$p))
@@ -7007,7 +7170,7 @@ return false
7007
7170
  }
7008
7171
  function enabled(value){return typeof value==="function"?value():value!==false
7009
7172
  }
7010
- function createV2Keymap(context){const [revision,setRevision]=createSignal27(0)
7173
+ function createV2Keymap(context){const [revision,setRevision]=createSignal29(0)
7011
7174
  const layers=new Map
7012
7175
  let sequence=0
7013
7176
  function layerActive(layer){const mode=layer.mode??"base"
@@ -7191,8 +7354,8 @@ context.ui.router.navigate({type:"plugin",name,data:params})
7191
7354
  },attention:context.attention,lifecycle:{signal:controller.signal,onDispose}}
7192
7355
  api.slots=createV2Slots(context,keymap,(dispose)=>{onDispose(dispose)
7193
7356
  })
7194
- setHostCapabilities(api,{todo:true,lsp:false,titleVisibility:false,unavailable:{todo:"OpenCode 2 does not expose Todo data through its supported plugin API."
7195
- }})
7357
+ setHostCapabilities(api,{todo:true,lsp:false,titleVisibility:false
7358
+ })
7196
7359
  return{api,async dispose(){controller.abort()
7197
7360
  for(let index=disposers.length-1;index>=0; index--)
7198
7361
  await disposers[index]()
@@ -7264,9 +7427,9 @@ if(item.source.target.startsWith("."))
7264
7427
  throw new Error("Unrecognized local Navigator source")
7265
7428
  await context.client.plugin.update({location:response.location,targets:[item.source.target]})
7266
7429
  }}}
7267
- async function setupNavigator(api,options,readProject){const config=pluginConfig(undefined)
7430
+ async function setupNavigator(api,options,readProject,managedTodo){const config=pluginConfig(undefined)
7268
7431
  const configured=await loadConfiguredDefaults(api,options,readProject)
7269
- const todo=createTodoController(api)
7432
+ const todo=managedTodo??createTodoController(api)
7270
7433
  const subagents=createSubagentController(api)
7271
7434
  const skills=createSkillController(api)
7272
7435
  const preferences=createPreferencesController(api,config,createPreferencesStore(api.state.path.state),configured)
@@ -7293,7 +7456,7 @@ api.slots.register({order:100,slots:{app(){return _$createComponent(IconProvider
7293
7456
  },interaction,skills,subagents,mcp
7294
7457
  }),_$createComponent(FirstRunWizardPersistence,{api,preferences
7295
7458
  }),_$createComponent(StartupSessionBinding,{api,controller:startup
7296
- }),_$createComponent(UpgradeNotesBinding,{api,preferences,version:"0.17.3"
7459
+ }),_$createComponent(UpgradeNotesBinding,{api,preferences,version:"0.18.1"
7297
7460
  }),_$createComponent(McpPersistence,{api,controller:mcp
7298
7461
  })]
7299
7462
  }})
@@ -7323,33 +7486,34 @@ throw result.error
7323
7486
  }
7324
7487
  return result.data?.type==="text"?result.data.content:undefined
7325
7488
  })
7326
- const versionStatus=createVersionStatus(api,"0.17.3")
7327
- const versionUpdates=createVersionUpdateActions(api,versionStatus,createOpenCodeV1VersionUpdater(api,meta),()=>preferences.rememberNavigatorVersionBeforeUpdate("0.17.3"))
7489
+ const versionStatus=createVersionStatus(api,"0.18.1")
7490
+ const versionUpdates=createVersionUpdateActions(api,versionStatus,createOpenCodeV1VersionUpdater(api,meta),()=>preferences.rememberNavigatorVersionBeforeUpdate("0.18.1"))
7328
7491
  api.slots.register({order:90,slots:{sidebar_footer(_context,props){return _$createComponent(IconProvider,{get style(){return preferences.lspIconStyle
7329
7492
  },get multilineCorners(){return preferences.cornerFont
7330
7493
  },get children(){return _$createComponent(VersionFooter,{api,get sessionID(){return props.session_id
7331
- },navigatorVersion:"0.17.3",status:versionStatus,get onOpenCodeUpdate(){return versionUpdates.openCode
7494
+ },navigatorVersion:"0.18.1",status:versionStatus,get onOpenCodeUpdate(){return versionUpdates.openCode
7332
7495
  },get onNavigatorUpdate(){return versionUpdates.navigator
7333
7496
  }})
7334
7497
  }})
7335
7498
  }}})
7336
7499
  }
7337
7500
  var setupOpenCodeV2=async (context)=>{const adapter=createOpenCodeV2Api(context)
7501
+ const todo=createNavigatorTodoController(context,adapter.api)
7338
7502
  const{mcp,preferences
7339
7503
  }=await setupNavigator(adapter.api,context.options,async ()=>{if(!context.client.file?.read)
7340
7504
  return
7341
7505
  return new TextDecoder().decode(await context.client.file.read({location:context.location,path:".opencode/navigator.json"
7342
7506
  }))
7343
- })
7344
- const versionStatus=createVersionStatus(adapter.api,"0.17.3")
7345
- const versionUpdates=createVersionUpdateActions(adapter.api,versionStatus,createOpenCodeV2VersionUpdater(context),()=>preferences.rememberNavigatorVersionBeforeUpdate("0.17.3"))
7507
+ },todo)
7508
+ const versionStatus=createVersionStatus(adapter.api,"0.18.1")
7509
+ const versionUpdates=createVersionUpdateActions(adapter.api,versionStatus,createOpenCodeV2VersionUpdater(context),()=>preferences.rememberNavigatorVersionBeforeUpdate("0.18.1"))
7346
7510
  const disposeFooter=context.ui.slot({append:"sidebar.footer",render:()=>_$createComponent(IconProvider,{get style(){return preferences.lspIconStyle
7347
7511
  },get multilineCorners(){return preferences.cornerFont
7348
7512
  },get children(){var _el$=_$createElement("box")
7349
7513
  _$setProp(_el$,"flexDirection","row")
7350
7514
  _$setProp(_el$,"justifyContent","space-between")
7351
7515
  _$insert(_el$,_$createComponent(VersionSummary,{get api(){return adapter.api
7352
- },navigatorVersion:"0.17.3",status:versionStatus,get onOpenCodeUpdate(){return versionUpdates.openCode
7516
+ },navigatorVersion:"0.18.1",status:versionStatus,get onOpenCodeUpdate(){return versionUpdates.openCode
7353
7517
  },get onNavigatorUpdate(){return versionUpdates.navigator
7354
7518
  }}),null)
7355
7519
  _$insert(_el$,_$createComponent(SettingsFooterButton,{get api(){return adapter.api
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://json.schemastore.org/package.json",
3
3
  "name": "opencode-navigator",
4
- "version": "0.17.3",
4
+ "version": "0.18.1",
5
5
  "description": "Search, navigate, and manage OpenCode sessions from the TUI",
6
6
  "type": "module",
7
7
  "packageManager": "bun@1.4.2",
@@ -27,6 +27,8 @@
27
27
  ],
28
28
  "files": [
29
29
  "dist",
30
+ "server.js",
31
+ "rpc.js",
30
32
  "assets",
31
33
  "scripts/install-font.mjs",
32
34
  "scripts/update-local-snapshot.mjs",
@@ -40,6 +42,12 @@
40
42
  "exports": {
41
43
  "./tui": {
42
44
  "import": "./dist/tui.js"
45
+ },
46
+ "./server": {
47
+ "import": "./dist/server.js"
48
+ },
49
+ "./rpc": {
50
+ "import": "./dist/rpc.js"
43
51
  }
44
52
  },
45
53
  "publishConfig": {
@@ -94,9 +102,9 @@
94
102
  }
95
103
  },
96
104
  "devDependencies": {
97
- "@opencode-ai/plugin": "1.18.32",
98
- "@opencode-ai/sdk": "1.18.32",
99
- "@opencode/plugin": "2.0.16",
105
+ "@opencode-ai/plugin": "1.18.34",
106
+ "@opencode-ai/sdk": "1.18.34",
107
+ "@opencode/plugin": "2.0.21",
100
108
  "@opentui/core": "0.5.12",
101
109
  "@opentui/keymap": "0.5.12",
102
110
  "@opentui/solid": "0.5.12",
package/rpc.js ADDED
@@ -0,0 +1 @@
1
+ export { NavigatorTodoRpc } from './dist/rpc.js'
package/server.js ADDED
@@ -0,0 +1 @@
1
+ export { default } from './dist/server.js'