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:
@@ -83,7 +83,6 @@ const tagMappings = {
|
||||
'routes/webhook.ts': { name: 'Webhook', description: 'Incoming webhooks' },
|
||||
'routes/auth.ts': { name: 'Auth', description: 'Authentication management' },
|
||||
'routes/devices.ts': { name: 'Devices', description: 'Device pairing and LAN peer operations' },
|
||||
'routes/coding-agents.ts': { name: 'Coding Agents', description: 'Coding agent installation, config, and runs' },
|
||||
'routes/api-docs.ts': { name: 'API Docs', description: 'OpenAPI route catalog' },
|
||||
}
|
||||
|
||||
@@ -213,8 +212,6 @@ function findMatchingBrace(content, openBrace) {
|
||||
}
|
||||
|
||||
function addEndpoint(paths, method, path, controllerMethod, tagInfo, content, matchIndex, controllerSource = '') {
|
||||
if (isInternalProxyRoute(path)) return
|
||||
|
||||
// Clean path parameters
|
||||
const openapiPath = path
|
||||
.replace(/:([^/]+)/g, '{$1}')
|
||||
@@ -250,10 +247,6 @@ function addEndpoint(paths, method, path, controllerMethod, tagInfo, content, ma
|
||||
paths[openapiPath][method] = operation
|
||||
}
|
||||
|
||||
function isInternalProxyRoute(path) {
|
||||
return path.startsWith('/api/codex-proxy/') || path.startsWith('/api/claude-code-proxy/')
|
||||
}
|
||||
|
||||
function generateParameters(openapiPath, source) {
|
||||
const params = []
|
||||
const seen = new Set()
|
||||
@@ -608,7 +601,7 @@ function schemaFromType(type) {
|
||||
const schema = {}
|
||||
|
||||
if (/\bnull\b/.test(normalized)) schema.nullable = true
|
||||
if (/SessionProviderApiMode|CodingAgentApiMode/.test(normalized)) {
|
||||
if (/SessionProviderApiMode/.test(normalized)) {
|
||||
return { ...schema, type: 'string', enum: ['chat_completions', 'codex_responses', 'anthropic_messages'] }
|
||||
}
|
||||
if (/string\[\]|Array<string>/.test(normalized)) {
|
||||
@@ -877,13 +870,13 @@ openapi.paths['/api/chat-run/runs'] = {
|
||||
},
|
||||
source: {
|
||||
type: 'string',
|
||||
enum: ['cli', 'coding_agent', 'global_agent'],
|
||||
description: 'Run backend source. Use cli for Hermes bridge runs, coding_agent for Claude Code/Codex, or global_agent for global-agent sessions. Omit source for normal Hermes chat runs; do not use the legacy api_server source.',
|
||||
enum: ['cli', 'global_agent', 'workflow'],
|
||||
description: 'Run backend source. Use cli for Hermes bridge runs or global_agent for global-agent sessions. Omit source for normal Hermes chat runs; do not use the legacy api_server source.',
|
||||
},
|
||||
session_source: {
|
||||
type: 'string',
|
||||
enum: ['global_agent'],
|
||||
description: 'Marks a coding-agent or bridge session as launched from the global agent.',
|
||||
enum: ['global_agent', 'workflow'],
|
||||
description: 'Marks the session as launched from the global agent or from a workflow run.',
|
||||
},
|
||||
instructions: {
|
||||
type: 'string',
|
||||
@@ -898,34 +891,6 @@ openapi.paths['/api/chat-run/runs'] = {
|
||||
type: 'string',
|
||||
description: 'Optional per-run reasoning effort override.',
|
||||
},
|
||||
coding_agent_id: {
|
||||
type: 'string',
|
||||
enum: ['claude-code', 'codex'],
|
||||
description: 'Coding agent id when source is coding_agent.',
|
||||
},
|
||||
agent_id: {
|
||||
type: 'string',
|
||||
enum: ['claude-code', 'codex'],
|
||||
description: 'Alias for coding_agent_id.',
|
||||
},
|
||||
mode: {
|
||||
type: 'string',
|
||||
enum: ['scoped', 'global'],
|
||||
description: 'Coding-agent launch mode.',
|
||||
},
|
||||
baseUrl: {
|
||||
type: 'string',
|
||||
description: 'Optional provider base URL for coding-agent runs.',
|
||||
},
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
description: 'Optional provider API key for coding-agent runs.',
|
||||
},
|
||||
apiMode: {
|
||||
type: 'string',
|
||||
enum: ['chat_completions', 'codex_responses', 'anthropic_messages'],
|
||||
description: 'Optional provider wire API mode for coding-agent runs.',
|
||||
},
|
||||
timeout_ms: {
|
||||
type: 'integer',
|
||||
minimum: 1,
|
||||
|
||||
+65
-40
@@ -279,10 +279,6 @@ const ALLOWED_LEGACY_BRAND = [
|
||||
file: 'packages/server/src/services/hermes/skill-injector.ts',
|
||||
reason: 'legacy manifest owner must stay recognized so managed skills are not re-injected',
|
||||
},
|
||||
{
|
||||
file: 'packages/server/src/services/coding-agents.ts',
|
||||
reason: 'legacy MCP server/command names are detected to clean up old on-disk config',
|
||||
},
|
||||
{
|
||||
file: 'packages/server/src/services/hermes/studio-mcp-autoinject.ts',
|
||||
reason: 'legacy MCP server/command names are detected to clean up old on-disk config',
|
||||
@@ -291,10 +287,6 @@ const ALLOWED_LEGACY_BRAND = [
|
||||
file: 'tests/server/studio-mcp-autoinject.test.ts',
|
||||
reason: 'asserts legacy MCP entries are cleaned from existing on-disk config',
|
||||
},
|
||||
{
|
||||
file: 'tests/server/coding-agents-launch.test.ts',
|
||||
reason: 'asserts legacy MCP entries are cleaned from existing on-disk config',
|
||||
},
|
||||
{
|
||||
file: 'tests/client/markdown-rendering.test.ts',
|
||||
reason: 'fixture URL is arbitrary sample data, not project branding',
|
||||
@@ -307,40 +299,20 @@ const ALLOWED_LEGACY_BRAND = [
|
||||
file: 'tests/server/agent-bridge-python-concurrency.test.ts',
|
||||
reason: 'asserts preview socket path derived from the preview endpoint env var',
|
||||
},
|
||||
{
|
||||
file: 'packages/server/src/services/runtime-version-manager.ts',
|
||||
reason: 'release download host and repo slug are the runtime-version lookup contract; replaced in Phase 008',
|
||||
},
|
||||
{
|
||||
file: 'packages/server/src/controllers/hermes/sessions.ts',
|
||||
reason: 'the visible-session-source check still lists coding_agent so sessions stored before Phase 003 stay listed',
|
||||
},
|
||||
{
|
||||
file: 'packages/client/src/shared/session-display.ts',
|
||||
reason: 'SOURCE_LABELS still maps the pre-Phase-003 coding_agent source so stored sessions render a label',
|
||||
},
|
||||
]
|
||||
|
||||
// Files whose remaining 'ekko' hits are the in-house agent runtime that Phase
|
||||
// 003 deletes outright. They are allow-listed here only until then; Phase 003
|
||||
// must delete these entries together with the files.
|
||||
const PHASE_003_EKKO_AGENT_FILES = [
|
||||
'packages/server/src/services/ekko-agent/manager.ts',
|
||||
'packages/server/src/services/ekko-agent/mcp.ts',
|
||||
'packages/server/src/services/hermes/run-chat/handle-ekko-agent-run.ts',
|
||||
'packages/server/src/services/hermes/run-chat/index.ts',
|
||||
'packages/server/src/services/hermes/run-chat/session-command.ts',
|
||||
'packages/server/src/services/hermes/run-chat/types.ts',
|
||||
'packages/client/src/api/coding-agents.ts',
|
||||
'packages/client/src/api/hermes/chat.ts',
|
||||
'packages/client/src/components/hermes/chat/ChatPanel.vue',
|
||||
'packages/client/src/components/hermes/chat/MessageList.vue',
|
||||
'packages/client/src/components/hermes/chat/SessionListItem.vue',
|
||||
'packages/client/src/stores/hermes/chat.ts',
|
||||
'packages/client/src/i18n/locales/en.ts',
|
||||
'packages/client/src/i18n/locales/zh.ts',
|
||||
'tests/ekko-agent',
|
||||
'tests/server/ekko-agent-manager.test.ts',
|
||||
'tests/server/ekko-agent-mcp.test.ts',
|
||||
'tests/server/run-chat-ekko-context.test.ts',
|
||||
'AGENTS.md',
|
||||
'ARCHITECTURE.md',
|
||||
]
|
||||
for (const target of PHASE_003_EKKO_AGENT_FILES) {
|
||||
ALLOWED_LEGACY_BRAND.push({
|
||||
file: target,
|
||||
reason: 'ekko-agent runtime is removed in Phase 003; this entry must be deleted then',
|
||||
})
|
||||
}
|
||||
|
||||
// Legacy identifiers that stay valid everywhere, because they are public
|
||||
// deployment contracts rather than project branding.
|
||||
const GLOBAL_ALLOWED_PATTERNS = [
|
||||
@@ -398,6 +370,59 @@ for (const hit of brandHits) {
|
||||
fail(`Legacy brand string must be removed (ekko / hermes-web-ui): ${hit}`)
|
||||
}
|
||||
|
||||
// --- Retired runtime scan ----------------------------------------------
|
||||
// Phase 003 removed the coding-agent and in-house agent runtimes. Every chat
|
||||
// session now runs through the Hermes bridge. A few identifiers survive only
|
||||
// so sessions, workflows, and history rows stored before Phase 003 keep
|
||||
// loading; RETIRED_RUNTIME_ALLOWED records why each one stays.
|
||||
const RETIRED_RUNTIME_PATTERNS = [
|
||||
/\bcoding_agent\b/,
|
||||
/\bcodingAgent[A-Za-z]/,
|
||||
/\bCodingAgent[A-Za-z]/,
|
||||
/\bekko-agent\b/,
|
||||
/\bekkoAgent[A-Za-z]/,
|
||||
/\bEkkoAgent[A-Za-z]/,
|
||||
]
|
||||
|
||||
const RETIRED_RUNTIME_ALLOWED = [
|
||||
{
|
||||
file: 'packages/server/src/controllers/hermes/sessions.ts',
|
||||
reason: 'isVisibleWebUiSessionSource still accepts the retired source so sessions stored before Phase 003 stay listed',
|
||||
},
|
||||
{
|
||||
file: 'packages/client/src/shared/session-display.ts',
|
||||
reason: 'SOURCE_LABELS still maps the retired source so stored sessions render a label',
|
||||
},
|
||||
]
|
||||
|
||||
function isAllowedRetiredRuntime(relativePath, line) {
|
||||
// Comments explain why the retired identifier still exists; only code hits matter.
|
||||
const trimmed = line.trim()
|
||||
if (trimmed.startsWith('//') || trimmed.startsWith('*') || trimmed.startsWith('/*')) return true
|
||||
const normalized = relativePath.split(path.sep).join('/')
|
||||
return RETIRED_RUNTIME_ALLOWED.some(
|
||||
entry => normalized === entry.file || normalized.startsWith(`${entry.file}/`),
|
||||
)
|
||||
}
|
||||
|
||||
const retiredHits = []
|
||||
for (const relativePath of scanFiles) {
|
||||
const normalized = relativePath.split(path.sep).join('/')
|
||||
if (SCAN_SKIP_FILES.has(normalized)) continue
|
||||
if (normalized.startsWith('docs/')) continue
|
||||
const text = await readFile(path.join(root, relativePath), 'utf8')
|
||||
const lines = text.split(/\r?\n/)
|
||||
for (let index = 0; index < lines.length; index += 1) {
|
||||
const line = lines[index]
|
||||
if (!RETIRED_RUNTIME_PATTERNS.some(pattern => pattern.test(line))) continue
|
||||
if (isAllowedRetiredRuntime(relativePath, line)) continue
|
||||
retiredHits.push(`${relativePath}:${index + 1}: ${line.trim()}`)
|
||||
}
|
||||
}
|
||||
for (const hit of retiredHits) {
|
||||
fail(`Retired runtime identifier must be removed (coding-agent / ekko-agent): ${hit}`)
|
||||
}
|
||||
|
||||
if (failures.length > 0) {
|
||||
console.error('Harness check failed:')
|
||||
for (const failure of failures) {
|
||||
|
||||
Reference in New Issue
Block a user