From 10fdec60378e5925ff0adffa6e7dcf09348bab13 Mon Sep 17 00:00:00 2001 From: Aditya Vikram Singh <247195684+avs-io@users.noreply.github.com> Date: Wed, 19 Aug 2026 19:42:42 +0530 Subject: [PATCH] fix(hermes): fail closed on unknown GLM, namespaces, and ACP identity MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Extra High review held #1039. Stop pricing every future glm-5.x as 5.2, stop collapsing unknown provider/org/model trees onto a priced leaf, and stop calling ACP Buzz — source=acp is a transport and the DB has no client field. Strip trailing punctuation on PR URLs and bump the Hermes parse version so old cache rows reparse. --- src/models.ts | 13 ++++--------- src/parser.ts | 2 +- src/providers/hermes.ts | 11 ++++------- src/session-cache.ts | 2 +- tests/models.test.ts | 3 ++- tests/providers/hermes.test.ts | 26 +++++++++++++++++++++++--- 6 files changed, 35 insertions(+), 22 deletions(-) diff --git a/src/models.ts b/src/models.ts index 1e30fb52..4d5d593c 100644 --- a/src/models.ts +++ b/src/models.ts @@ -630,8 +630,7 @@ function routedModelCandidates(model: string): string[] { ids.push(value) } push(model) - let current = getCanonicalName(model) - push(current) + let current = model let peeled = true while (peeled) { peeled = false @@ -644,13 +643,9 @@ function routedModelCandidates(model: string): string[] { } } } - if (current.includes('/')) push(current.slice(current.lastIndexOf('/') + 1)) - const leaf = current.includes('/') ? current.slice(current.lastIndexOf('/') + 1) : current - if (/^glm-5(?:\.\d+)?$/i.test(leaf)) { - push('glm-5p2') - push('glm-5p1') - push('glm-5') - } + // One house-style vendor strip (anthropic/foo → foo). Do not keep + // collapsing unknown provider/org/model trees onto a priced leaf. + push(getCanonicalName(current)) return ids } diff --git a/src/parser.ts b/src/parser.ts index 3aa1adc9..481f6d93 100644 --- a/src/parser.ts +++ b/src/parser.ts @@ -2493,7 +2493,7 @@ function providerCallToCachedCall(call: ParsedProviderCall): CachedCall { webSearchRequests: call.webSearchRequests, cacheCreationOneHourTokens: 0, }, - costUSD: (call.provider === 'mistral-vibe' || call.provider === 'antigravity' || call.provider === 'devin' || call.provider === 'vercel-gateway' || call.provider === 'hermes' || call.provider === 'buzz' || call.provider === 'kiro' || call.provider === 'codewhale' || call.provider === 'quickdesk' || call.provider === 'cline-cli') ? call.costUSD : undefined, + costUSD: (call.provider === 'mistral-vibe' || call.provider === 'antigravity' || call.provider === 'devin' || call.provider === 'vercel-gateway' || call.provider === 'hermes' || call.provider === 'kiro' || call.provider === 'codewhale' || call.provider === 'quickdesk' || call.provider === 'cline-cli') ? call.costUSD : undefined, isEstimated: call.costIsEstimated || undefined, speed: call.speed, timestamp: call.timestamp, diff --git a/src/providers/hermes.ts b/src/providers/hermes.ts index e8d42a0b..77e910c3 100644 --- a/src/providers/hermes.ts +++ b/src/providers/hermes.ts @@ -99,7 +99,8 @@ function extractGithubPullUrls(...texts: Array): stri if (!text) continue for (const match of text.matchAll(re)) { try { - const url = new URL(match[0]) + const raw = match[0].replace(/[).,\]]:;]+$/g, '') + const url = new URL(raw) if (url.protocol !== 'https:') continue if (!/^\/[^/]+\/[^/]+\/pull\/\d+$/.test(url.pathname)) continue found.add(`${url.origin}${url.pathname}`) @@ -280,15 +281,11 @@ function isRealWorkspace(cwd: string | null | undefined): cwd is string { return true } -function hermesSurfaceProvider(source: string | null | undefined): 'hermes' | 'buzz' { - return source === 'acp' ? 'buzz' : 'hermes' -} - function resolveHermesWorkspace( row: HermesSessionRow, messages: HermesMessageRow[], -): { project: string; projectPath?: string; provider: 'hermes' | 'buzz' } { - const provider = hermesSurfaceProvider(row.source) +): { project: string; projectPath?: string; provider: 'hermes' } { + const provider = 'hermes' as const const repo = row.git_repo_root?.trim() if (isRealWorkspace(repo)) { return { project: sanitizeProject(basename(repo)), projectPath: repo, provider } diff --git a/src/session-cache.ts b/src/session-cache.ts index 79e8ee2b..b10c9652 100644 --- a/src/session-cache.ts +++ b/src/session-cache.ts @@ -292,7 +292,7 @@ export const PROVIDER_PARSE_VERSIONS: Record = { // replays (double-counted before), takes the model from the reporting // assistant/message, and keeps agent-injected context out of the preview. dsh: 'seed-aware-v1', - hermes: 'reasoning-output-accounting-v1-est-cost', + hermes: 'reasoning-output-accounting-v1-est-cost-routed-ids-workspace-pr-v2', 'lingtai-tui': 'token-ledger-registry-activity-v3', 'ibm-bob': 'worktree-project-grouping-v1', // project-path-v1: the parser now records the session's full working diff --git a/tests/models.test.ts b/tests/models.test.ts index ff7b6711..e75ae30c 100644 --- a/tests/models.test.ts +++ b/tests/models.test.ts @@ -71,8 +71,9 @@ describe('getModelCosts', () => { expect(upper!.outputCostPerToken).toBe(sibling!.outputCostPerToken) expect(getModelCosts('cp/cline-pass/glm-5.3')!.inputCostPerToken).toBe(sibling!.inputCostPerToken) expect(getModelCosts('omniroute:cp/cline-pass/glm-5.3')!.inputCostPerToken).toBe(sibling!.inputCostPerToken) - expect(getModelCosts('glm-5.4')!.inputCostPerToken).toBe(sibling!.inputCostPerToken) expect(getModelCosts('cmd/deepseek/deepseek-v4-flash')).not.toBeNull() + expect(getModelCosts('glm-5.4')).toBeNull() + expect(getModelCosts('provider/org/glm-5.3')).toBeNull() }) }) diff --git a/tests/providers/hermes.test.ts b/tests/providers/hermes.test.ts index f6363afd..e08062ce 100644 --- a/tests/providers/hermes.test.ts +++ b/tests/providers/hermes.test.ts @@ -520,6 +520,26 @@ skipUnlessSqlite('hermes provider', () => { expect(calls[0]?.project).toBe('hermes') }) + it('captures GitHub pull URLs that are wrapped in prose punctuation', async () => { + const dbPath = createHermesDb(tmpDir) + withTestDb(dbPath, (db) => { + insertSession(db, { + id: 'pr-punct', + inputTokens: 10, + outputTokens: 5, + cacheReadTokens: 0, + cacheWriteTokens: 0, + reasoningTokens: 0, + startedAt: 1779549200, + }) + db.prepare('INSERT INTO messages (session_id, role, content, timestamp) VALUES (?, ?, ?, ?)') + .run('pr-punct', 'assistant', 'See (https://github.com/getagentseal/codeburn/pull/1037).', 1779549201) + }) + + const calls = await collectCalls(tmpDir, `${dbPath}#hermes-session=pr-punct`) + expect(calls[0]?.prLinks).toEqual(['https://github.com/getagentseal/codeburn/pull/1037']) + }) + it('ignores GitHub pull URLs that only appear in tool dumps', async () => { const dbPath = createHermesDb(tmpDir) withTestDb(dbPath, (db) => { @@ -542,7 +562,7 @@ skipUnlessSqlite('hermes provider', () => { expect(calls[0]?.prLinks).toBeUndefined() }) - it('treats ACP sessions as the Buzz app, not a Hermes folder', async () => { + it('keeps ACP sessions on Hermes because source=acp is a transport, not Buzz', async () => { const dbPath = createHermesDb(tmpDir) withTestDb(dbPath, (db) => { insertSession(db, { @@ -561,8 +581,8 @@ skipUnlessSqlite('hermes provider', () => { const calls = await collectCalls(tmpDir, `${dbPath}#hermes-session=acp-session`) expect(calls[0]).toMatchObject({ - provider: 'buzz', - project: 'buzz', + provider: 'hermes', + project: 'hermes', }) })