Newer
Older
navi-1 / webclient / src / stores / chat / terminals.js
/**
 * Terminal metadata mirrored from terminal_* events: the terminals store the
 * panel renders, plus live output attached to the matching pending tool card.
 */

export function createTerminalTracking(state) {
  const { terminals, streamingMsg } = state

  function onTerminalOpened(data) {
    const name = data.terminal_name
    if (!name) return
    const prev = terminals.value[name] || {}
    terminals.value = { ...terminals.value, [name]: { ...prev, name, closed: false } }
  }

  function onTerminalOutput(data) {
    const name = data.terminal_name
    if (!name) return
    const prev = terminals.value[name] || { output: '' }
    const next = {
      ...prev,
      output: (prev.output || '') + (data.delta || ''),
    }
    terminals.value = { ...terminals.value, [name]: next }
    // Also try to attach live output to the matching pending tool card
    const msg = streamingMsg.value
    if (msg) {
      for (let i = msg.tools.length - 1; i >= 0; i--) {
        const t = msg.tools[i]
        if (t.kind === 'tool' && t.name === 'terminal' && t.terminalName === name) {
          t.terminalOutput = next.output
          break
        }
      }
    }
  }

  function onTerminalClosed(data) {
    const name = data.terminal_name
    if (!name) return
    const prev = terminals.value[name] || {}
    const next = {
      ...prev,
      closed: true,
      closeReason: data.reason || 'unknown',
    }
    terminals.value = { ...terminals.value, [name]: next }
    // Mark matching pending tool card as no longer running
    const msg = streamingMsg.value
    if (msg) {
      for (let i = msg.tools.length - 1; i >= 0; i--) {
        const t = msg.tools[i]
        if (t.kind === 'tool' && t.name === 'terminal' && t.terminalName === name && t.pending) {
          t.pending = false
          t.success = data.reason !== 'error'
          break
        }
      }
    }
  }

  return { onTerminalOpened, onTerminalOutput, onTerminalClosed }
}