Phase 003: remove the coding-agent and ekko-agent runtimes
Every chat session now runs through the Hermes bridge, so the three chat chains become feature-equivalent instead of three partially-maintained dispatch paths. Keeping the coding-agent and in-house agent runtimes alive meant every bridge change had to be mirrored into two more runtimes that had already drifted. Historical rows are read, not migrated: sessions stored with the retired source normalize to cli on load, workflow graphs naming the retired agents keep loading and run on the bridge, and the visible-source check plus SOURCE_LABELS still recognize the old value so those sessions stay listed. The public surface follows the code: the OpenAPI generator and the MCP chat-run tool schema drop the six retired entry fields so external callers are not told about runtimes that no longer exist. harness-check gains a RETIRED_RUNTIME scan so the residual check is a mechanical gate instead of a manual grep, with the two surviving read-side compat sites recorded with reasons. Refs: docs/phases/phase_003.md
This commit is contained in:
@@ -294,12 +294,9 @@ describe('chat store reasoning/tool boundaries', () => {
|
||||
])
|
||||
})
|
||||
|
||||
it('settles running coding-agent tools when the run completes without a tool.completed event', async () => {
|
||||
it('settles running tools when the run completes without a tool.completed event', async () => {
|
||||
const store = useChatStore()
|
||||
const session = makeSession()
|
||||
session.source = 'coding_agent'
|
||||
session.agent = 'claude'
|
||||
session.codingAgentId = 'claude-code'
|
||||
store.sessions = [session]
|
||||
store.activeSessionId = 'session-1'
|
||||
store.activeSession = session
|
||||
@@ -351,49 +348,6 @@ describe('chat store reasoning/tool boundaries', () => {
|
||||
].join('\n'))
|
||||
})
|
||||
|
||||
it('queues active coding agent follow-up messages without command styling', async () => {
|
||||
const store = useChatStore()
|
||||
const session = makeSession()
|
||||
session.source = 'coding_agent'
|
||||
session.agent = 'claude'
|
||||
session.codingAgentId = 'claude-code'
|
||||
store.sessions = [session]
|
||||
store.activeSessionId = 'session-1'
|
||||
store.activeSession = session
|
||||
|
||||
await store.sendMessage('first input')
|
||||
const onEvent = chatApi.startRunViaSocket.mock.calls[0][1] as (event: RunEvent) => void
|
||||
onEvent({
|
||||
event: 'agent.event',
|
||||
session_id: 'session-1',
|
||||
source: 'coding_agent',
|
||||
kind: 'status',
|
||||
text: 'Input sent to coding agent.',
|
||||
})
|
||||
expect(store.messages).toHaveLength(1)
|
||||
|
||||
onEvent({ event: 'run.started', session_id: 'session-1' })
|
||||
|
||||
await store.sendMessage('/not-a-hermes-command')
|
||||
|
||||
expect(chatApi.startRunViaSocket).toHaveBeenCalledTimes(2)
|
||||
expect(store.queuedUserMessages.get('session-1')).toEqual([
|
||||
expect.objectContaining({
|
||||
role: 'user',
|
||||
content: '/not-a-hermes-command',
|
||||
queued: true,
|
||||
systemType: undefined,
|
||||
}),
|
||||
])
|
||||
expect(store.messages).toEqual([
|
||||
expect.objectContaining({
|
||||
role: 'user',
|
||||
content: 'first input',
|
||||
queued: false,
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
it('queues unknown slash commands in bridge sessions as normal user input', async () => {
|
||||
const store = useChatStore()
|
||||
const session = makeSession()
|
||||
@@ -484,111 +438,14 @@ describe('chat store reasoning/tool boundaries', () => {
|
||||
])
|
||||
})
|
||||
|
||||
it('starts global coding-agent runs without provider credentials', async () => {
|
||||
it('sends the selected workspace and reasoning effort when starting a bridge run', async () => {
|
||||
const store = useChatStore()
|
||||
const session = makeSession()
|
||||
session.source = 'coding_agent'
|
||||
session.agent = 'codex'
|
||||
session.codingAgentId = 'codex'
|
||||
session.codingAgentMode = 'global'
|
||||
session.provider = 'should-not-send'
|
||||
session.model = 'should-not-send'
|
||||
session.baseUrl = 'http://example.invalid'
|
||||
session.apiKey = 'secret'
|
||||
session.apiMode = 'chat_completions'
|
||||
session.reasoningEffort = 'high'
|
||||
store.sessions = [session]
|
||||
store.activeSessionId = 'session-1'
|
||||
store.activeSession = session
|
||||
|
||||
await store.sendMessage('use global codex auth')
|
||||
|
||||
const body = chatApi.startRunViaSocket.mock.calls[0][0]
|
||||
expect(body).toEqual(expect.objectContaining({
|
||||
source: 'coding_agent',
|
||||
coding_agent_id: 'codex',
|
||||
mode: 'global',
|
||||
}))
|
||||
expect(body.provider).toBeUndefined()
|
||||
expect(body.model).toBeUndefined()
|
||||
expect(body.baseUrl).toBeUndefined()
|
||||
expect(body.apiKey).toBeUndefined()
|
||||
expect(body.apiMode).toBeUndefined()
|
||||
expect(body.reasoning_effort).toBeUndefined()
|
||||
})
|
||||
|
||||
it('clears stale coding-agent runtime credentials when switching providers', async () => {
|
||||
const store = useChatStore()
|
||||
const session = makeSession()
|
||||
session.source = 'coding_agent'
|
||||
session.agent = 'codex'
|
||||
session.codingAgentId = 'codex'
|
||||
session.codingAgentMode = 'scoped'
|
||||
session.provider = 'xiaomi'
|
||||
session.model = 'mimo-v2.5-pro'
|
||||
session.baseUrl = 'https://api.xiaomimimo.com/v1'
|
||||
session.apiKey = 'sk-xiaomi'
|
||||
session.apiMode = 'chat_completions'
|
||||
store.sessions = [session]
|
||||
store.activeSessionId = 'session-1'
|
||||
store.activeSession = session
|
||||
|
||||
const ok = await store.switchSessionModel('deepseek-v4-pro', 'deepseek', 'session-1', 'chat_completions')
|
||||
|
||||
expect(ok).toBe(true)
|
||||
expect(sessionsApi.setSessionModel).toHaveBeenCalledWith(
|
||||
'session-1',
|
||||
'deepseek-v4-pro',
|
||||
'deepseek',
|
||||
'chat_completions',
|
||||
)
|
||||
expect(session.provider).toBe('deepseek')
|
||||
expect(session.model).toBe('deepseek-v4-pro')
|
||||
expect(session.baseUrl).toBeUndefined()
|
||||
expect(session.apiKey).toBeUndefined()
|
||||
expect(session.apiMode).toBe('chat_completions')
|
||||
})
|
||||
|
||||
|
||||
it('preserves a scoped coding-agent API mode when reselecting the same provider', async () => {
|
||||
const store = useChatStore()
|
||||
const session = makeSession()
|
||||
session.source = 'coding_agent'
|
||||
session.agent = 'codex'
|
||||
session.codingAgentId = 'codex'
|
||||
session.codingAgentMode = 'scoped'
|
||||
session.provider = 'fun-codex'
|
||||
session.model = 'gpt-5.4'
|
||||
session.apiMode = 'chat_completions'
|
||||
store.sessions = [session]
|
||||
store.activeSessionId = 'session-1'
|
||||
store.activeSession = session
|
||||
|
||||
const ok = await store.switchSessionModel('gpt-5.5', 'fun-codex', 'session-1')
|
||||
|
||||
expect(ok).toBe(true)
|
||||
expect(sessionsApi.setSessionModel).toHaveBeenCalledWith(
|
||||
'session-1',
|
||||
'gpt-5.5',
|
||||
'fun-codex',
|
||||
'chat_completions',
|
||||
)
|
||||
expect(session.apiMode).toBe('chat_completions')
|
||||
})
|
||||
|
||||
it('sends the selected workspace when starting a coding-agent run', async () => {
|
||||
const store = useChatStore()
|
||||
const session = makeSession()
|
||||
session.source = 'coding_agent'
|
||||
session.agent = 'claude'
|
||||
session.codingAgentId = 'claude-code'
|
||||
session.codingAgentMode = 'scoped'
|
||||
session.source = 'cli'
|
||||
session.provider = 'openrouter'
|
||||
session.model = 'anthropic/claude-sonnet-4.6'
|
||||
session.baseUrl = 'https://openrouter.ai/api/v1'
|
||||
session.apiKey = 'sk-test'
|
||||
session.apiMode = 'anthropic_messages'
|
||||
session.workspace = '/workspace/project-a'
|
||||
session.reasoningEffort = 'high'
|
||||
store.sessions = [session]
|
||||
store.activeSessionId = 'session-1'
|
||||
store.activeSession = session
|
||||
@@ -596,21 +453,62 @@ describe('chat store reasoning/tool boundaries', () => {
|
||||
await store.sendMessage('use this project')
|
||||
|
||||
expect(chatApi.startRunViaSocket.mock.calls[0][0]).toEqual(expect.objectContaining({
|
||||
source: 'coding_agent',
|
||||
coding_agent_id: 'claude-code',
|
||||
mode: 'scoped',
|
||||
source: 'cli',
|
||||
workspace: '/workspace/project-a',
|
||||
reasoning_effort: 'high',
|
||||
}))
|
||||
})
|
||||
|
||||
it('keeps a local stream controller for active coding-agent runs', async () => {
|
||||
it('sends only the bridge run fields on the run payload', async () => {
|
||||
const store = useChatStore()
|
||||
const session = makeSession()
|
||||
store.sessions = [session]
|
||||
store.activeSessionId = 'session-1'
|
||||
store.activeSession = session
|
||||
|
||||
await store.sendMessage('hello')
|
||||
|
||||
expect(Object.keys(chatApi.startRunViaSocket.mock.calls[0][0] as Record<string, unknown>).sort())
|
||||
.toEqual([
|
||||
'input',
|
||||
'model',
|
||||
'model_groups',
|
||||
'profile',
|
||||
'provider',
|
||||
'queue_id',
|
||||
'reasoning_effort',
|
||||
'session_id',
|
||||
'source',
|
||||
'workspace',
|
||||
])
|
||||
})
|
||||
|
||||
it('switches the session model without touching runtime credentials', async () => {
|
||||
const store = useChatStore()
|
||||
const session = makeSession()
|
||||
session.provider = 'xiaomi'
|
||||
session.model = 'mimo-v2.5-pro'
|
||||
store.sessions = [session]
|
||||
store.activeSessionId = 'session-1'
|
||||
store.activeSession = session
|
||||
|
||||
const ok = await store.switchSessionModel('deepseek-v4-pro', 'deepseek', 'session-1')
|
||||
|
||||
expect(ok).toBe(true)
|
||||
expect(sessionsApi.setSessionModel).toHaveBeenCalledWith(
|
||||
'session-1',
|
||||
'deepseek-v4-pro',
|
||||
'deepseek',
|
||||
)
|
||||
expect(session.provider).toBe('deepseek')
|
||||
expect(session.model).toBe('deepseek-v4-pro')
|
||||
})
|
||||
|
||||
it('keeps a local stream controller for active runs', async () => {
|
||||
const abort = vi.fn()
|
||||
chatApi.startRunViaSocket.mockReturnValue({ abort })
|
||||
const store = useChatStore()
|
||||
const session = makeSession()
|
||||
session.source = 'coding_agent'
|
||||
session.agent = 'claude'
|
||||
session.codingAgentId = 'claude-code'
|
||||
store.sessions = [session]
|
||||
store.activeSessionId = 'session-1'
|
||||
store.activeSession = session
|
||||
|
||||
Reference in New Issue
Block a user