mirror of
https://github.com/Ryder-Sun/Meldwork.git
synced 2026-11-07 06:56:03 -10:00
fix(agents): enable OpenCodeReview direct chats
Allow ready OpenCodeReview installations to appear in the sidebar and be opened as direct conversations for user review instructions. Route explicit OpenCodeReview sends through the read-only code-review runtime so writable direct conversation defaults do not create a capability mismatch, while continuing to avoid native session persistence. Verification: npm --prefix frontend test; npm --prefix desktop test; npm --prefix frontend run build; npm --prefix frontend run build:desktop; npm --prefix desktop run pack.
This commit is contained in:
1 parent
c475b50c93
commit
9fb52fb710
14 files changed
+148
-66
No files matched your search
@@ -93,7 +93,7 @@ Specialized review:
|
||||
|
||||
**OpenCodeReview**
|
||||
|
||||
Availability depends on the installed Agent, supported version, authentication state, and declared capabilities. OpenCodeReview is a specialized review target rather than a general conversation Agent.
|
||||
Availability depends on the installed Agent, supported version, authentication state, and declared capabilities. OpenCodeReview can be shown in the left navigation and opened as a direct chat for review instructions, while its runtime stays scoped to read-only diff-focused code review.
|
||||
|
||||
Each Agent keeps its own capabilities, provider configuration, permission model, and session behavior. Availability depends on the installed Agent, its version, and its authentication state.
|
||||
|
||||
|
||||
+1
-1
@@ -92,7 +92,7 @@ Meldwork 当前已支持:
|
||||
专项审查:
|
||||
**OpenCodeReview**
|
||||
|
||||
能否实际使用取决于本机安装情况、受支持版本、认证状态和 Agent 声明的能力。OpenCodeReview 是专项审查目标,不作为通用对话 Agent 使用。
|
||||
能否实际使用取决于本机安装情况、受支持版本、认证状态和 Agent 声明的能力。OpenCodeReview 可以显示在左侧导航栏,也可以作为单聊接收审查指令;运行时仍限定为只读的 Git diff 代码审查。
|
||||
|
||||
每个 Agent 保留自己的能力、Provider、权限模型和 Session 行为。能否使用取决于本机是否安装、版本是否兼容,以及对应认证是否就绪。
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ const {
|
||||
AGENT_PERMISSION_MODES,
|
||||
AGENT_TOOL_CLASSES,
|
||||
agentRuntimeCapabilities,
|
||||
isCodeReviewAgentKind,
|
||||
} = require('./agent-runtime-contract.cjs')
|
||||
const { parseFitMatrix } = require('../evaluations/eval-records.cjs')
|
||||
const { cleanInline, isSupportedAgentKind } = require('../workspace/local-workspace-contracts.cjs')
|
||||
@@ -331,18 +332,24 @@ class AgentRouter {
|
||||
)) {
|
||||
throw routingError('LOCAL_AGENT_ROUTING_CONTEXT_CONFLICT')
|
||||
}
|
||||
const requirements = normalizeRoutingRequirements(input.routingRequirements, {
|
||||
domains: effectiveMode === 'automatic' ? ['general'] : [],
|
||||
inputTypes,
|
||||
outputTypes: ['text'],
|
||||
permissionMode: group.allowWrite === true ? 'workspace-write' : 'read-only',
|
||||
minContextChars,
|
||||
collaboration: mode === 'auto' ? 'required' : 'none',
|
||||
})
|
||||
const approvedKinds = targetList(group.agentKinds)
|
||||
const explicitTargets = effectiveMode === 'explicit' && !requestedTargets.length
|
||||
? approvedKinds
|
||||
: requestedTargets
|
||||
const explicitReadOnlyTargets = effectiveMode === 'explicit'
|
||||
&& explicitTargets.length > 0
|
||||
&& explicitTargets.every(kind => isCodeReviewAgentKind(kind))
|
||||
const permissionMode = explicitReadOnlyTargets
|
||||
? 'read-only'
|
||||
: group.allowWrite === true ? 'workspace-write' : 'read-only'
|
||||
const requirements = normalizeRoutingRequirements(input.routingRequirements, {
|
||||
domains: effectiveMode === 'automatic' ? ['general'] : [],
|
||||
inputTypes,
|
||||
outputTypes: ['text'],
|
||||
permissionMode,
|
||||
minContextChars,
|
||||
collaboration: mode === 'auto' ? 'required' : 'none',
|
||||
})
|
||||
if (explicitTargets.some(kind => !approvedKinds.includes(kind))) {
|
||||
const kind = explicitTargets.find(target => !approvedKinds.includes(target))
|
||||
throw routingError('LOCAL_MESSAGE_TARGET_OUT_OF_GROUP', { kind })
|
||||
|
||||
@@ -119,6 +119,7 @@ const AGENT_RUNTIME_CAPABILITIES = Object.freeze({
|
||||
})
|
||||
|
||||
const DEFAULT_RUNTIME_CAPABILITIES = generalProfile({ resumable: false })
|
||||
const REVIEW_ONLY_AGENT_KINDS = Object.freeze(new Set())
|
||||
|
||||
function boundedValues(value, allowed, fallback) {
|
||||
if (!Array.isArray(value) || !value.length) return [...fallback]
|
||||
@@ -298,6 +299,10 @@ function requireTerminalAgentResult(result) {
|
||||
}
|
||||
|
||||
function isReviewOnlyAgentKind(kind) {
|
||||
return REVIEW_ONLY_AGENT_KINDS.has(kind)
|
||||
}
|
||||
|
||||
function isCodeReviewAgentKind(kind) {
|
||||
return agentRuntimeCapabilities(kind).task === 'code_review'
|
||||
}
|
||||
|
||||
@@ -316,6 +321,7 @@ module.exports = {
|
||||
agentRuntimeCapabilities,
|
||||
agentRuntimeError,
|
||||
agentRuntimeFailure,
|
||||
isCodeReviewAgentKind,
|
||||
isReviewOnlyAgentKind,
|
||||
normalizeAgentResult,
|
||||
normalizeExternalRunRef,
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
const {
|
||||
agentRuntimeCapabilities,
|
||||
isReviewOnlyAgentKind,
|
||||
} = require('../agents/agent-runtime-contract.cjs')
|
||||
|
||||
const RECENT_VERIFICATION_MS = 24 * 60 * 60 * 1000
|
||||
@@ -137,8 +136,7 @@ class LocalWorkspaceAgentCatalog {
|
||||
task: capabilities.task,
|
||||
resumable: capabilities.resumable,
|
||||
capabilities,
|
||||
showInSidebar: capabilities.task === 'general'
|
||||
&& available
|
||||
showInSidebar: available
|
||||
&& (typeof preferred === 'boolean' ? preferred : true),
|
||||
}
|
||||
})
|
||||
@@ -151,7 +149,6 @@ class LocalWorkspaceAgentCatalog {
|
||||
setSidebarVisibility(kind, visible) {
|
||||
const agent = this.detectedAgents().find(item => item.kind === kind)
|
||||
if (!agent) throw new Error('LOCAL_AGENT_NOT_INSTALLED')
|
||||
if (isReviewOnlyAgentKind(agent.kind)) throw new Error('LOCAL_AGENT_REVIEW_ONLY')
|
||||
if (visible && !agent.available) throw new Error('LOCAL_AGENT_UNAVAILABLE')
|
||||
const state = this.state()
|
||||
state.agentPreferences[kind] = { showInSidebar: Boolean(visible) }
|
||||
@@ -186,8 +183,7 @@ class LocalWorkspaceAgentCatalog {
|
||||
? 'runtime-auth-failure'
|
||||
: 'unverified'
|
||||
const preferred = state.agentPreferences[kind]?.showInSidebar
|
||||
agent.showInSidebar = !isReviewOnlyAgentKind(agent.kind)
|
||||
&& agent.available
|
||||
agent.showInSidebar = agent.available
|
||||
&& (typeof preferred === 'boolean' ? preferred : true)
|
||||
}
|
||||
this.save()
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
const { createHash, randomUUID } = require('node:crypto')
|
||||
const {
|
||||
isReviewOnlyAgentKind,
|
||||
isCodeReviewAgentKind,
|
||||
requireTerminalAgentResult,
|
||||
} = require('../agents/agent-runtime-contract.cjs')
|
||||
const {
|
||||
@@ -273,9 +273,6 @@ class LocalWorkspaceAgentInvocation {
|
||||
}
|
||||
|
||||
async invoke(group, kind, mode, signal, threadRootId = '', context = {}) {
|
||||
if (isReviewOnlyAgentKind(kind) && context.taskType !== 'code_review') {
|
||||
throw new Error('LOCAL_AGENT_REVIEW_ONLY')
|
||||
}
|
||||
const activeRun = this.activeRuns.get(group.id)
|
||||
const taskId = cleanText(activeRun?.taskId || context.taskId || threadRootId, 120)
|
||||
if (!taskId) throw new Error('LOCAL_RUN_TASK_INVALID')
|
||||
@@ -326,10 +323,7 @@ class LocalWorkspaceAgentInvocation {
|
||||
) {
|
||||
const agent = this.detectedAgents().find(item => item.kind === kind && item.available)
|
||||
if (!agent) throw new Error('LOCAL_AGENT_UNAVAILABLE')
|
||||
const reviewOnly = isReviewOnlyAgentKind(kind)
|
||||
if (reviewOnly && context.taskType !== 'code_review') {
|
||||
throw new Error('LOCAL_AGENT_REVIEW_ONLY')
|
||||
}
|
||||
const reviewOnly = isCodeReviewAgentKind(kind)
|
||||
const isolated = isolatedInvocationContext(context)
|
||||
const internal = context.internal === true
|
||||
if (internal && !isolated) throw new Error('LOCAL_RUN_INTERNAL_CONTEXT_INVALID')
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
const path = require('node:path')
|
||||
const { isReviewOnlyAgentKind } = require('../agents/agent-runtime-contract.cjs')
|
||||
const { normalizeTraceCapsule } = require('../runs/run-harness.cjs')
|
||||
const { normalizeRoutingDecision } = require('../agents/agent-routing.cjs')
|
||||
const {
|
||||
@@ -72,7 +71,7 @@ class LocalWorkspaceConversations {
|
||||
|
||||
createGroup(input) {
|
||||
const available = new Set(this.detectedAgents()
|
||||
.filter(agent => agent.available && !isReviewOnlyAgentKind(agent.kind))
|
||||
.filter(agent => agent.available)
|
||||
.map(agent => agent.kind))
|
||||
const conversationType = input.conversationType === 'direct' ? 'direct' : 'group'
|
||||
const directAgentKind = conversationType === 'direct'
|
||||
@@ -118,7 +117,7 @@ class LocalWorkspaceConversations {
|
||||
let nextAgentKinds = group.agentKinds
|
||||
if (input.agentKinds != null) {
|
||||
const available = new Set(this.detectedAgents()
|
||||
.filter(agent => agent.available && !isReviewOnlyAgentKind(agent.kind))
|
||||
.filter(agent => agent.available)
|
||||
.map(agent => agent.kind))
|
||||
const kinds = [...new Set(input.agentKinds.filter(kind => available.has(kind)))]
|
||||
if (!kinds.length) throw new Error('LOCAL_GROUP_AGENT_REQUIRED')
|
||||
|
||||
@@ -107,6 +107,25 @@ test('explicit specialized Agents are not assigned an invented general-domain re
|
||||
)
|
||||
})
|
||||
|
||||
test('explicit OpenCodeReview targets keep writable direct conversations routable as read-only review work', () => {
|
||||
const decision = new AgentRouter().route(request({
|
||||
agents: [agent('opencodereview')],
|
||||
group: {
|
||||
id: 'group-routing',
|
||||
agentKinds: ['opencodereview'],
|
||||
allowWrite: true,
|
||||
},
|
||||
input: { targetKinds: ['opencodereview'] },
|
||||
}))
|
||||
|
||||
assert.deepEqual(decision.selectedKinds, ['opencodereview'])
|
||||
assert.equal(decision.requirements.permissionMode, 'read-only')
|
||||
assert.deepEqual(
|
||||
decision.candidates.find(candidate => candidate.kind === 'opencodereview').exclusions,
|
||||
[],
|
||||
)
|
||||
})
|
||||
|
||||
test('explicit Connector Agents use their declared runtime capabilities', () => {
|
||||
const connector = agent('custom-bbbbbbbbbbbbbbbb', {
|
||||
capabilities: {
|
||||
|
||||
@@ -5,6 +5,7 @@ const {
|
||||
AGENT_RUNTIME_CAPABILITIES,
|
||||
agentRuntimeError,
|
||||
agentRuntimeFailure,
|
||||
isCodeReviewAgentKind,
|
||||
isReviewOnlyAgentKind,
|
||||
normalizeExternalRunRef,
|
||||
outcomeForAcpStopReason,
|
||||
@@ -27,7 +28,8 @@ test('runtime capabilities cover every supported built-in Agent', () => {
|
||||
assert.ok(Number.isSafeInteger(capabilities.contextLimitChars), kind)
|
||||
assert.equal(typeof capabilities.resumable, 'boolean', kind)
|
||||
}
|
||||
assert.equal(isReviewOnlyAgentKind('opencodereview'), true)
|
||||
assert.equal(isCodeReviewAgentKind('opencodereview'), true)
|
||||
assert.equal(isReviewOnlyAgentKind('opencodereview'), false)
|
||||
assert.equal(AGENT_RUNTIME_CAPABILITIES.opencodereview.resumable, false)
|
||||
})
|
||||
|
||||
|
||||
@@ -270,17 +270,16 @@ test('sidebar and runtime credential mutations preserve persistence and event or
|
||||
assert.equal(agents()[0].availabilitySource, 'unverified')
|
||||
})
|
||||
|
||||
test('review-only Agents cannot be enabled in the conversation sidebar', () => {
|
||||
test('OpenCodeReview can be enabled in the conversation sidebar', () => {
|
||||
const reviewAgent = {
|
||||
kind: 'opencodereview', available: true, credentialState: 'ready', showInSidebar: false,
|
||||
}
|
||||
const { catalog, events } = fixture({ initialAgents: [reviewAgent] })
|
||||
const { agents, catalog, events, state } = fixture({ initialAgents: [reviewAgent] })
|
||||
|
||||
assert.throws(
|
||||
() => catalog.setSidebarVisibility('opencodereview', true),
|
||||
{ message: 'LOCAL_AGENT_REVIEW_ONLY' },
|
||||
)
|
||||
assert.deepEqual(events, [])
|
||||
assert.deepEqual(catalog.setSidebarVisibility('opencodereview', true), { agents: [reviewAgent] })
|
||||
assert.deepEqual(events, ['save', 'emit', 'snapshot'])
|
||||
assert.equal(state.agentPreferences.opencodereview.showInSidebar, true)
|
||||
assert.equal(agents()[0].showInSidebar, true)
|
||||
})
|
||||
|
||||
test('runtime credential cleanup removes only missing entries and never emits', () => {
|
||||
|
||||
@@ -96,8 +96,8 @@ test('workspace startup rejects orphaned Human Gates after restoring interrupted
|
||||
})
|
||||
})
|
||||
|
||||
test('review-only Agents cannot join ordinary direct or group conversations', async (t) => {
|
||||
const { directory, options } = fixture()
|
||||
test('OpenCodeReview can be shown in the sidebar and receive direct instructions', async (t) => {
|
||||
const { directory, calls, options } = fixture()
|
||||
t.after(() => fs.rmSync(directory, { recursive: true, force: true }))
|
||||
options.detectAgents = async () => [
|
||||
{ kind: 'codex', name: 'Codex', executable: '/tmp/codex', version: '1.0.0' },
|
||||
@@ -105,35 +105,46 @@ test('review-only Agents cannot join ordinary direct or group conversations', as
|
||||
kind: 'opencodereview', name: 'OpenCodeReview', executable: '/tmp/ocr', version: '1.8.6',
|
||||
},
|
||||
]
|
||||
options.runAgent = async (agent, prompt, workdir, runOptions) => {
|
||||
calls.push({ agent, prompt, workdir, runOptions })
|
||||
return {
|
||||
text: 'Review completed.', sessionRef: 'ignored-review-session', outcome: 'completed',
|
||||
externalRunRef: 'ocr-review-123',
|
||||
}
|
||||
}
|
||||
const workspace = new LocalWorkspace(options)
|
||||
const snapshot = await workspace.refreshAgents()
|
||||
const reviewAgent = snapshot.agents.find(agent => agent.kind === 'opencodereview')
|
||||
assert.equal(reviewAgent.available, true)
|
||||
assert.equal(reviewAgent.task, 'code_review')
|
||||
assert.equal(reviewAgent.showInSidebar, false)
|
||||
assert.equal(reviewAgent.showInSidebar, true)
|
||||
|
||||
assert.throws(() => workspace.createGroup({
|
||||
workspace.setSidebarVisibility('opencodereview', false)
|
||||
assert.equal(workspace.snapshot().agents.find(agent => agent.kind === 'opencodereview').showInSidebar, false)
|
||||
workspace.setSidebarVisibility('opencodereview', true)
|
||||
assert.equal(workspace.snapshot().agents.find(agent => agent.kind === 'opencodereview').showInSidebar, true)
|
||||
|
||||
const directGroup = workspace.createGroup({
|
||||
conversationType: 'direct', directAgentKind: 'opencodereview', workdir: directory,
|
||||
}), { message: 'LOCAL_GROUP_AGENT_REQUIRED' })
|
||||
assert.throws(() => workspace.createGroup({
|
||||
name: 'General discussion', agentKinds: ['opencodereview'], workdir: directory,
|
||||
}), { message: 'LOCAL_GROUP_AGENT_REQUIRED' })
|
||||
const generalGroup = workspace.createGroup({
|
||||
name: 'General discussion', agentKinds: ['codex'], workdir: directory,
|
||||
})
|
||||
assert.throws(
|
||||
() => workspace.updateGroup(generalGroup.id, { agentKinds: ['opencodereview'] }),
|
||||
{ message: 'LOCAL_GROUP_AGENT_REQUIRED' },
|
||||
)
|
||||
await assert.rejects(
|
||||
workspace.invokeAgent(
|
||||
{ id: 'forged-group', workdir: directory, allowWrite: false },
|
||||
'opencodereview',
|
||||
'manual',
|
||||
new AbortController().signal,
|
||||
),
|
||||
{ message: 'LOCAL_AGENT_REVIEW_ONLY' },
|
||||
)
|
||||
assert.deepEqual(directGroup.agentKinds, ['opencodereview'])
|
||||
|
||||
await workspace.sendMessage({
|
||||
groupId: directGroup.id,
|
||||
text: 'Review the current diff and focus on regressions.',
|
||||
targetKinds: ['opencodereview'],
|
||||
})
|
||||
|
||||
assert.equal(calls.length, 1)
|
||||
assert.equal(calls[0].agent.kind, 'opencodereview')
|
||||
assert.match(calls[0].prompt, /Review the current diff and focus on regressions\./)
|
||||
assert.equal(calls[0].runOptions.sandbox, 'read-only')
|
||||
assert.equal(calls[0].runOptions.sessionRef, '')
|
||||
assert.equal(Object.keys(workspace.state.sessions).length, 0)
|
||||
assert.equal(Object.keys(workspace.state.sessionMeta).length, 0)
|
||||
const reply = workspace.snapshot().messages.find(message => message.role === 'agent')
|
||||
assert.equal(reply.agentKind, 'opencodereview')
|
||||
assert.equal(reply.trace.context.externalRunRef, 'ocr-review-123')
|
||||
})
|
||||
|
||||
test('an explicit internal code-review task runs once and remains read-only', async (t) => {
|
||||
|
||||
@@ -103,27 +103,46 @@ describe('RoundRelay workbench', () => {
|
||||
wrapper.unmount()
|
||||
})
|
||||
|
||||
it('keeps review-only Agents in Settings but out of ordinary conversations', async () => {
|
||||
const { wrapper } = await mountApp(({ state }) => {
|
||||
it('keeps OpenCodeReview visible for ordinary conversations and direct instructions', async () => {
|
||||
const { wrapper, bridge } = await mountApp(({ state, bridge: desktopBridge }) => {
|
||||
state.agents.push({
|
||||
kind: 'opencodereview',
|
||||
installed: true,
|
||||
available: true,
|
||||
credentialState: 'ready',
|
||||
showInSidebar: false,
|
||||
showInSidebar: true,
|
||||
task: 'code_review',
|
||||
resumable: false,
|
||||
version: '1.8.6',
|
||||
})
|
||||
desktopBridge.localWorkspace.createGroup.mockImplementation(async input => ({
|
||||
id: 'direct-opencodereview',
|
||||
createdAt: '2026-07-29T08:00:00Z',
|
||||
updatedAt: '2026-07-29T08:00:00Z',
|
||||
...structuredClone(input),
|
||||
}))
|
||||
})
|
||||
|
||||
expect(wrapper.findAll('.home-agent-item').map(item => item.text())).not.toContain('OpenCodeReview')
|
||||
expect(wrapper.findAll('.home-agent-item').some(item => item.text().includes('OpenCodeReview'))).toBe(true)
|
||||
await wrapper.get('.new-group-button').trigger('click')
|
||||
expect(wrapper.findAll('.agent-choice').map(choice => choice.text())).not.toContain('OpenCodeReview')
|
||||
expect(wrapper.findAll('.agent-choice').some(choice => choice.text().includes('OpenCodeReview'))).toBe(true)
|
||||
await wrapper.get('.modal-header .icon-button').trigger('click')
|
||||
await flushPromises()
|
||||
expect(wrapper.findAll('.sidebar-agent').some(agent => agent.text().includes('OpenCodeReview'))).toBe(true)
|
||||
|
||||
await wrapper.get('.sidebar-settings-entry').trigger('click')
|
||||
expect(wrapper.get('.agent-manager').text()).toContain('OpenCodeReview')
|
||||
const card = wrapper.findAll('.settings-agent-card')
|
||||
.find(node => node.text().includes('OpenCodeReview'))
|
||||
await card.findAll('button').find(button => button.text().includes('Open chat')).trigger('click')
|
||||
await flushPromises()
|
||||
|
||||
expect(bridge.localWorkspace.createGroup).toHaveBeenCalledWith(expect.objectContaining({
|
||||
conversationType: 'direct',
|
||||
directAgentKind: 'opencodereview',
|
||||
name: 'OpenCodeReview',
|
||||
allowWrite: true,
|
||||
}))
|
||||
wrapper.unmount()
|
||||
})
|
||||
|
||||
@@ -806,6 +825,12 @@ describe('RoundRelay workbench', () => {
|
||||
expect(source).toMatch(/\.agent-card-meta-row\s*\{[^}]*display:\s*flex;[^}]*padding:\s*0 16px 14px 77px;/s)
|
||||
expect(source).toMatch(/\.settings-agent-actions\s*\{[^}]*flex-wrap:\s*nowrap;[^}]*padding:\s*0;/s)
|
||||
expect(source).toMatch(/\.settings-agent-card\.focused\s*\{[^}]*box-shadow:\s*none;/s)
|
||||
expect(source).toMatch(/\.message-row\.agent \.message-body:hover \.message-meta-actions > button,[^{]+\.message-row\.agent \.message-body:hover \.message-footer-actions > button/s)
|
||||
expect(source).toMatch(/\.message-meta-actions > button::after,[^{]+\.message-footer-actions > button::after,[^{]+\.code-copy-button::after\s*\{[^}]*bottom:\s*calc\(100% \+ 6px\);[^}]*background:\s*var\(--surface-raised\);/s)
|
||||
expect(source).toMatch(/\.message-footer-actions > button::after\s*\{[^}]*top:\s*calc\(100% \+ 6px\);[^}]*bottom:\s*auto;/s)
|
||||
expect(source).toMatch(/\.conversation-link\s*\{[^}]*grid-template-columns:\s*26px minmax\(0, 1fr\) 58px;/s)
|
||||
expect(source).toMatch(/\.conversation-empty-wordmark\s*\{[^}]*width:\s*min\(276px, 60vw\);/s)
|
||||
expect(source).toMatch(/\.conversation-empty-copy\s*\{[^}]*width:\s*min\(920px, 100%\);[^}]*min-height:\s*60px;/s)
|
||||
expect(source).not.toContain('img[src$="/hermes.svg"],\n:root[data-theme="dark"] img[src$="/opencode.svg"]')
|
||||
expect(composerSource).toContain('composer-attachment-button')
|
||||
expect(composerSource).toContain('composer-skill-button')
|
||||
|
||||
@@ -17,6 +17,32 @@ function catalogFor(agent) {
|
||||
}
|
||||
|
||||
describe('Agent compatibility state', () => {
|
||||
it('keeps ready OpenCodeReview available for sidebar and composer targets', () => {
|
||||
setLocale('en')
|
||||
const catalog = useAgentCatalog({
|
||||
activeGroup: ref(null),
|
||||
directGroupsFor: () => [],
|
||||
installCatalog: ref({ agents: [{ kind: 'opencodereview', installed: true }] }),
|
||||
snapshot: ref({
|
||||
agents: [{
|
||||
kind: 'opencodereview',
|
||||
installed: true,
|
||||
available: true,
|
||||
credentialState: 'ready',
|
||||
showInSidebar: true,
|
||||
task: 'code_review',
|
||||
version: '1.8.6',
|
||||
}],
|
||||
}),
|
||||
t,
|
||||
theme: ref('light'),
|
||||
})
|
||||
|
||||
expect(catalog.readyAgents.value.map(agent => agent.kind)).toContain('opencodereview')
|
||||
expect(catalog.sidebarAgents.value.map(agent => agent.kind)).toContain('opencodereview')
|
||||
expect(catalog.readyAgentKinds.value.has('opencodereview')).toBe(true)
|
||||
})
|
||||
|
||||
it.each([
|
||||
['LOCAL_AGENT_VERSION_UNSUPPORTED', 'Unsupported version'],
|
||||
['LOCAL_AGENT_REQUIRED_CAPABILITY_MISSING', 'Required capability missing'],
|
||||
|
||||
@@ -40,9 +40,7 @@ export function useAgentCatalog({
|
||||
agents: mergedCatalog.value.filter(agent => !agent.custom),
|
||||
},
|
||||
])
|
||||
const readyAgents = computed(() => mergedCatalog.value.filter(
|
||||
agent => agent.ready && agent.task !== 'code_review',
|
||||
))
|
||||
const readyAgents = computed(() => mergedCatalog.value.filter(agent => agent.ready))
|
||||
const readyAgentSignature = computed(() => readyAgents.value.map(agent => agent.kind).join('\u0000'))
|
||||
const readyAgentKinds = computed(() => new Set(readyAgents.value.map(agent => agent.kind)))
|
||||
const readyCount = computed(() => readyAgents.value.length)
|
||||
|
||||
Reference in new issue
Block a user