Skip to content

Pinia Task 10 - product-expert-operator-agent #6827

@n-lark

Description

@n-lark

Task 10 — product-expert-operator-agent (PR 11)

Gate: Phase 0 merged
Vuex module: frontend/src/store/modules/product/expert/operator-agent/index.js
New file: frontend/src/stores/product-expert-operator-agent.js
Persistence: sessionId → localStorage (cleared on logout)
Cross-store dependency: getCapabilities reads account.team.id via _account-bridge.js

10.1 — Create the Pinia store

// frontend/src/stores/product-expert-operator-agent.js
import { defineStore } from 'pinia'
import { markRaw } from 'vue'
import expertApi from '../api/expert.js'
import { useAccountBridge } from './_account-bridge.js'

export const useProductExpertOperatorAgentStore = defineStore('product-expert-operator-agent', {
    state: () => ({
        sessionId: null,
        messages: [],
        sessionStartTime: null,
        sessionWarningShown: false,
        sessionExpiredShown: false,
        sessionCheckTimer: null,
        capabilityServers: [],   // NOTE: named capabilityServers to avoid conflict with the capabilities getter
        selectedCapabilities: []
    }),
    getters: {
        capabilities: (state) => state.capabilityServers.map(c => ({
            ...c,
            toolCount: c.resources.length + c.tools.length + c.prompts.length
        }))
    },
    actions: {
        reset () {
            if (this.sessionCheckTimer) clearInterval(this.sessionCheckTimer)
            Object.assign(this, {
                sessionId: null, messages: [], sessionStartTime: null,
                sessionWarningShown: false, sessionExpiredShown: false,
                sessionCheckTimer: null, capabilityServers: [], selectedCapabilities: []
            })
        },
        setSelectedCapabilities (caps) { this.selectedCapabilities = caps },
        setSessionCheckTimer (timer) { this.sessionCheckTimer = markRaw(timer) },
        async getCapabilities () {
            const { team } = useAccountBridge()
            const data = await expertApi.getCapabilities({ context: { teamId: team.id } })
            this.capabilityServers = data.servers || []
            // NOTE: waitWhile() guard from the Vuex module is intentionally dropped —
            // it was a hacky workaround for issue #6520 / #6519 and is not carried forward.
        }
    },
    persist: {
        pick: ['sessionId'],
        storage: localStorage
    }
})

10.2 — No component consumers

Only accessed by the parent product-expert store.

10.3 — Do not delete the Vuex module yet

Delete with the parent in PR 12.

Logout bridge: uncomment useProductExpertOperatorAgentStore().$reset() in the Vuex logout action (Task 0.7).

10.4 — Write store tests

// frontend/src/tests/stores/product-expert-operator-agent.spec.js
import { setActivePinia, createPinia } from 'pinia'
import { describe, it, expect, beforeEach, vi } from 'vitest'
import { useProductExpertOperatorAgentStore } from '@/stores/product-expert-operator-agent.js'
import * as bridge from '@/stores/_account-bridge.js'
import expertApi from '@/api/expert.js'

describe('product-expert-operator-agent store', () => {
    beforeEach(() => {
        setActivePinia(createPinia())
        vi.spyOn(bridge, 'useAccountBridge').mockReturnValue({ team: { id: 'team-1' } })
    })

    it('initializes with empty capabilities and messages', () => {
        const store = useProductExpertOperatorAgentStore()
        expect(store.capabilities).toEqual([])
        expect(store.messages).toEqual([])
    })

    it('getCapabilities fetches and stores server list', async () => {
        const store = useProductExpertOperatorAgentStore()
        const server = { id: 'srv-1', resources: [], tools: [], prompts: [] }
        vi.spyOn(expertApi, 'getCapabilities').mockResolvedValue({ servers: [server] })
        await store.getCapabilities()
        // capabilities getter maps capabilityServers and adds toolCount
        expect(store.capabilities).toEqual([{ ...server, toolCount: 0 }])
        expect(store.capabilityServers).toEqual([server])
    })

    it('reset clears timer and resets state', () => {
        const store = useProductExpertOperatorAgentStore()
        const fakeTimer = setInterval(() => {}, 9999)
        const clearSpy = vi.spyOn(globalThis, 'clearInterval')
        store.setSessionCheckTimer(fakeTimer)
        store.reset()
        expect(clearSpy).toHaveBeenCalledWith(fakeTimer)
        expect(store.sessionId).toBeNull()
        clearInterval(fakeTimer)
    })
})

10.5 — Export from stores index

export { useProductExpertOperatorAgentStore } from './product-expert-operator-agent.js'

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

Status

Done

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions