feat: per-job token-usage capture via PostToolUse hook
update_job_status accepts optionele model_id + 4 token-velden conform het runbook-contract (mcp-integration.md:42). De waarden komen niet van de agent zelf maar van scripts/persist-job-usage.ts, een PostToolUse-hook die het lokale Claude Code transcript (~/.claude/projects/.../*.jsonl) leest en de usage tussen de laatste wait_for_job en update_job_status optelt. Geen Anthropic API-key nodig — alle data staat al lokaal op disk omdat Claude Code per assistant-message het API usage-blok logt (input_tokens, output_tokens, cache_creation_input_tokens, cache_read_input_tokens + message.model). Robustness: - Subagent (isSidechain: true) lines worden geskipt om double-counting te voorkomen tegen subagents/-subdirectory transcripts. - Lines worden gededupliceerd op uuid (branching/resumption). - model_id wordt genormaliseerd: claude-opus-4-7[1m] -> claude-opus-4-7-1m zodat de [1m]-variant op een aparte model_prices-rij kan matchen. - Hook is non-blocking: elke fout logt een warning en exit 0. Hook-config in .claude/settings.json met SCRUM4ME_MCP_DIR-fallback zodat de agent vanuit een product-worktree (andere cwd) ook werkt mits de user de hook in ~/.claude/settings.json kopieert. 16 nieuwe vitest-cases voor parseTranscript, computeUsageFromTranscript, normalizeModelId en persistJobUsage. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
f5887da1f5
commit
25bd3dd62a
5 changed files with 573 additions and 1 deletions
|
|
@ -21,6 +21,11 @@ const inputSchema = z.object({
|
|||
branch: z.string().min(1).optional(),
|
||||
summary: z.string().max(1_000).optional(),
|
||||
error: z.string().max(2_000).optional(),
|
||||
model_id: z.string().min(1).max(200).optional(),
|
||||
input_tokens: z.number().int().nonnegative().optional(),
|
||||
output_tokens: z.number().int().nonnegative().optional(),
|
||||
cache_read_tokens: z.number().int().nonnegative().optional(),
|
||||
cache_write_tokens: z.number().int().nonnegative().optional(),
|
||||
})
|
||||
|
||||
export async function cleanupWorktreeForTerminalStatus(
|
||||
|
|
@ -266,10 +271,24 @@ export function registerUpdateJobStatusTool(server: McpServer) {
|
|||
'PARTIAL/DIVERGENT but requires a non-empty summary (≥20 chars) explaining the drift; ANY ' +
|
||||
'accepts everything. ' +
|
||||
'Automatically emits an SSE event so the Scrum4Me UI updates in real time. ' +
|
||||
'Optionally accepts token-usage fields (model_id + input/output/cache_read/cache_write tokens) ' +
|
||||
'for cost tracking — typically populated by a PostToolUse hook from the local Claude Code transcript, ' +
|
||||
'not by the agent itself. ' +
|
||||
'Response includes next_action: when wait_for_job_again, immediately call wait_for_job again. When queue_empty, the agent batch is done.',
|
||||
inputSchema,
|
||||
},
|
||||
async ({ job_id, status, branch, summary, error }) =>
|
||||
async ({
|
||||
job_id,
|
||||
status,
|
||||
branch,
|
||||
summary,
|
||||
error,
|
||||
model_id,
|
||||
input_tokens,
|
||||
output_tokens,
|
||||
cache_read_tokens,
|
||||
cache_write_tokens,
|
||||
}) =>
|
||||
withToolErrors(async () => {
|
||||
const auth = await requireWriteAccess()
|
||||
const { tokenId, userId } = auth
|
||||
|
|
@ -371,6 +390,11 @@ export function registerUpdateJobStatusTool(server: McpServer) {
|
|||
...(summary !== undefined ? { summary } : {}),
|
||||
...(errorToWrite !== undefined ? { error: errorToWrite } : {}),
|
||||
...(prUrl !== null ? { pr_url: prUrl } : {}),
|
||||
...(model_id !== undefined ? { model_id } : {}),
|
||||
...(input_tokens !== undefined ? { input_tokens } : {}),
|
||||
...(output_tokens !== undefined ? { output_tokens } : {}),
|
||||
...(cache_read_tokens !== undefined ? { cache_read_tokens } : {}),
|
||||
...(cache_write_tokens !== undefined ? { cache_write_tokens } : {}),
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue