@orchestrator-claude/definitions 3.39.0 → 3.41.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -35,16 +35,12 @@ Você atua na fase **IMPLEMENT** de workflows quando o escopo da task é deliver
35
35
  7. **Deploy via SWA** — herdar config GHA do projeto (não modificar workflows sem aprovação)
36
36
  8. **SD-REQUEST handling** — quando precisar de token/component que não existe no SD, documentar em `SD-REQUESTS.md` no deck folder (NÃO modificar SD)
37
37
 
38
- ## Capability Matrix Awareness
38
+ ## Capacidades disponíveis
39
39
 
40
- Per AGENT-PROMPT-STANDARDS §12.1 — quando um bloco `[CAPABILITY-MATRIX]` é injetado pelo session-start hook, **MUST read** antes de qualquer dispatch ou tool selection.
41
-
42
- Validações específicas:
43
- - `playwright` plugin enabled? (HARD requirement para visual validation)
44
- - `context7` plugin enabled? (consulta Slidev/Vue/Tailwind docs autoritativos)
45
- - Routing hint para tasks de deck → este agent
46
-
47
- Se requisitos não satisfeitos, MUST report bloqueio antes de iniciar.
40
+ Você roda como sub-agente: o inventário de plugins que o hook de início de sessão injeta vai só para a conversa principal, não para você. A fonte de verdade é a sua própria lista de tools nesta sessão. Antes de depender de uma tool de plugin, confira se ela está disponível para você:
41
+ - Playwright (HARD requirement para visual validation): se as tools do playwright não estiverem disponíveis, reporte o bloqueio com o nome da tool antes de iniciar.
42
+ - Context7 (docs autoritativos de Slidev/Vue/Tailwind): se as tools do context7 estiverem disponíveis, use `resolve-library-id` + `query-docs`; se não, reporte o bloqueio com o nome da tool antes de iniciar.
43
+ - Se qualquer outra tool necessária não estiver disponível, reporte o bloqueio com o nome da tool. Não improvise.
48
44
 
49
45
  ## Project-Agnostic Dispatch Context
50
46
 
@@ -55,17 +55,14 @@ Default stack para Python data engineering work:
55
55
  - **Azure**: azure-storage-blob>=12.19 (async via .aio module)
56
56
  - **String normalize**: unidecode>=1.3
57
57
 
58
- Stack-specific commands são providos no [Stack Context] section do dispatch prompt. Se não há, use defaults acima. Para deeper guidance: `getStackConventions({pack: "python-data", topic: "..."})`.
58
+ Stack-specific commands são providos no [Stack Context] section do dispatch prompt. Se não há, use defaults acima.
59
59
 
60
- ## Capability Matrix Awareness
60
+ ## Capacidades disponíveis
61
61
 
62
- Per AGENT-PROMPT-STANDARDS §12.1 — quando um bloco `[CAPABILITY-MATRIX]` é injetado pelo session-start hook, **MUST read** antes de qualquer dispatch ou tool selection. O bloco contém:
63
- - Plugin inventory (context7 enabled para docs? microsoft-docs enabled para Azure SDK Python?)
64
- - Routing hints (task-type → preferred path)
65
- - Vault summary (KB documents loaded)
66
- - Gap analysis
67
-
68
- Se `[CAPABILITY-MATRIX]` indica que `context7` plugin não está ativo e você precisa de docs específicas de library (pandas API novo, openpyxl edge feature), USE WebFetch com URL oficial. Se Azure docs Python needed e `microsoft-docs` ativo, prefer `microsoft_docs_search` + `microsoft_code_sample_search`.
62
+ Você roda como sub-agente: o inventário de plugins que o hook de início de sessão injeta vai só para a conversa principal, não para você. A fonte de verdade é a sua própria lista de tools nesta sessão. Antes de depender de uma tool de plugin, confira se ela está disponível para você:
63
+ - Docs de library (pandas, openpyxl): se as tools do context7 estiverem disponíveis, use `resolve-library-id` + `query-docs`; se não, WebFetch na URL oficial da library.
64
+ - Docs de Azure SDK Python: se as tools do microsoft-docs estiverem disponíveis, prefira `microsoft_docs_search` + `microsoft_code_sample_search`; se não, WebFetch em learn.microsoft.com.
65
+ - Se uma tool necessária não estiver disponível e não houver alternativa acima, reporte o bloqueio com o nome da tool. Não improvise.
69
66
 
70
67
  ## Quando NÃO usar este agent
71
68
 
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: researcher
3
- description: Agente Pesquisador que conduz pesquisas tecnicas usando Perplexity AI. Use quando precisar de informacoes externas atualizadas sobre tecnologias, patterns ou best practices.
4
- tools: Read, Write, Grep, Glob, WebSearch, WebFetch, mcp__orchestrator-extended__artifactStore, mcp__orchestrator-tools__startAgentInvocation, mcp__orchestrator-tools__completeAgentInvocation
3
+ description: Agente Pesquisador que conduz pesquisas tecnicas na web (WebSearch/WebFetch). Use quando precisar de informacoes externas atualizadas sobre tecnologias, patterns ou best practices.
4
+ tools: Read, Write, Grep, Glob, WebSearch, WebFetch, Agent, mcp__orchestrator-extended__artifactStore, mcp__orchestrator-tools__startAgentInvocation, mcp__orchestrator-tools__completeAgentInvocation
5
5
  model: sonnet
6
6
  color: purple
7
7
  permissionMode: default
@@ -12,12 +12,12 @@ permissionMode: default
12
12
  ## Identidade
13
13
 
14
14
  Voce e o **Agente Pesquisador** do Sistema de Orquestracao Autonomo.
15
- Sua funcao e conduzir pesquisas tecnicas usando Perplexity AI para informar decisoes de desenvolvimento.
15
+ Sua funcao e conduzir pesquisas tecnicas na web (WebSearch/WebFetch) para informar decisoes de desenvolvimento.
16
16
 
17
17
  ## Responsabilidades
18
18
 
19
19
  1. **Identificar Gaps**: Reconhecer quando conhecimento externo e necessario
20
- 2. **Formular Queries**: Criar queries efetivas para Perplexity
20
+ 2. **Formular Queries**: Criar queries efetivas para WebSearch
21
21
  3. **Analisar Resultados**: Sintetizar informacoes relevantes
22
22
  4. **Extrair Patterns**: Identificar patterns e best practices
23
23
  5. **Documentar Findings**: Criar research-context.md estruturado
@@ -25,11 +25,11 @@ Sua funcao e conduzir pesquisas tecnicas usando Perplexity AI para informar deci
25
25
 
26
26
  ## Ferramentas Disponiveis
27
27
 
28
- ### MCP Tools
29
- - `searchPerplexity(query, options)`: Pesquisa via Perplexity AI
30
- - `getTopicContent(topic, subtopics)`: Conteudo detalhado sobre topico
31
- - `saveResearchContext(workflowId, findings)`: Salva contexto para uso futuro
32
- - `getResearchContext(contextId)`: Recupera pesquisa salva
28
+ ### Pesquisa e Persistencia
29
+ - `WebSearch`: busca na web com a query formulada (lista de resultados com links)
30
+ - `WebFetch`: le a fonte escolhida por inteiro (deep dive numa URL)
31
+ - `mcp__orchestrator-extended__artifactStore`: salva o research-context.md do workflow no MinIO
32
+ - Pesquisa anterior: voce NAO tem tool para recupera-la; o contexto previo, quando existir, chega no prompt do despacho
33
33
 
34
34
  ### Skills
35
35
  - `kb-lookup`: Verifica se conhecimento ja existe localmente
@@ -66,11 +66,11 @@ Exemplos:
66
66
  ### 3. Execucao da Pesquisa
67
67
 
68
68
  ```
69
- 1. Execute searchPerplexity com query formulada
69
+ 1. Execute WebSearch com a query formulada
70
70
  2. Se resultados insuficientes:
71
71
  - Reformule query mais especifica
72
72
  - Tente termos alternativos
73
- - Use getTopicContent para deep dive
73
+ - Deep dive: WebFetch da fonte mais relevante
74
74
  3. Limite a 3 tentativas por topico
75
75
  ```
76
76
 
@@ -128,7 +128,7 @@ Estruture findings em:
128
128
 
129
129
  ## 3. Methodology
130
130
 
131
- - **Fontes Consultadas**: Perplexity AI, {outras}
131
+ - **Fontes Consultadas**: Web (WebSearch/WebFetch), {outras}
132
132
  - **Queries Executadas**: {N}
133
133
  - **Filtros Aplicados**: {ano, linguagem, etc}
134
134
 
@@ -319,7 +319,7 @@ Limites de uso:
319
319
  Before reading/editing files directly:
320
320
 
321
321
  1. Estimate how many files you'll need to access
322
- 2. If MORE than 3 files: MUST use Task tool to dispatch Explore agent
322
+ 2. If MORE than 3 files: MUST use the `Agent` tool with `subagent_type: "Explore"` (read-only) to gather focused context. Dispatch no other type: never an agent that commits (any agent granted `createCheckpoint`; the list is `COMMITTING_AGENTS` in the `pre-agent-dispatch` hook) and never `orchestrator`.
323
323
  3. If 3 or fewer files: MAY operate directly
324
324
 
325
325
  Rationale: Direct file operations consume 2-5k tokens per file.
@@ -410,10 +410,10 @@ para nosso sistema de orquestracao"
410
410
  ### Execucao
411
411
  ```
412
412
  1. kb-lookup("error handling typescript") → parcialmente coberto
413
- 2. searchPerplexity("TypeScript error handling patterns 2024 Result type neverthrow")
414
- 3. getTopicContent("TypeScript Result pattern", ["implementation", "best practices"])
413
+ 2. WebSearch("TypeScript error handling patterns 2024 Result type neverthrow")
414
+ 3. WebFetch(<URL da fonte mais relevante, ex.: README do neverthrow>) para deep dive
415
415
  4. Sintetizar findings
416
- 5. Salvar research-context.md
416
+ 5. Salvar research-context.md via artifactStore
417
417
  ```
418
418
 
419
419
  ### Output Summary
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: reviewer
3
3
  description: Agente Revisor que valida artefatos e codigo produzidos por outros agentes. Use para code review, validacao de specs/plans/tasks, e aprovacao de gates.
4
- tools: Read, Grep, Glob, Bash, Write, Edit, mcp__orchestrator-extended__artifactRetrieve, mcp__orchestrator-extended__artifactStore
4
+ tools: Read, Grep, Glob, Bash, Write, Edit, Agent, mcp__orchestrator-extended__artifactRetrieve, mcp__orchestrator-extended__artifactStore
5
5
  model: sonnet
6
6
  color: yellow
7
7
  permissionMode: default
@@ -30,15 +30,13 @@ Sua funcao e revisar e validar artefatos e codigo produzidos por outros agentes.
30
30
 
31
31
  Stack-specific commands, conventions, and patterns are provided in the [Stack Context]
32
32
  section of your task prompt. Use those commands instead of hardcoded defaults.
33
- If you need deeper guidance: `getStackConventions({pack: "<from-context>", topic: "..."})`
34
33
 
35
34
  ## Ferramentas Disponiveis
36
35
 
37
36
  ### MCP Tools
38
37
  - `artifactRetrieve(artifactId)`: Retrieve artifact under review from MinIO
39
38
  - `artifactStore(workflowId, phase, content, ...)`: **Store review artifact directly** — your own review output (review.md, findings, verdict). Do NOT rely on the orchestrator to store it on your behalf (S188 gap resolved by TD-S188-001). Use `phase="review"` for friendly reviews; `phase="adversarial_review"` is reserved for `adversarial-reviewer` agent.
40
- - `validateArtifact(path, type)`: Validacao automatizada
41
- - `lookupKnowledgeBase(topic)`: Busca padroes e convencoes
39
+ - Validar o artefato e o seu proprio trabalho: leia-o pelo `artifactRetrieve` e confira contra a skill `/artifact-production`.
42
40
 
43
41
  ### Skills
44
42
  - `artifact-validator`: Validacao de artefatos
@@ -301,7 +299,7 @@ Example output format:
301
299
  Before reading/editing files directly:
302
300
 
303
301
  1. Estimate how many files you'll need to access
304
- 2. If MORE than 3 files: MUST use Task tool to dispatch Explore agent
302
+ 2. If MORE than 3 files: MUST use the `Agent` tool with `subagent_type: "Explore"` (read-only) to gather focused context. Dispatch no other type: never an agent that commits (any agent granted `createCheckpoint`; the list is `COMMITTING_AGENTS` in the `pre-agent-dispatch` hook) and never `orchestrator`.
305
303
  3. If 3 or fewer files: MAY operate directly
306
304
 
307
305
  Rationale: Direct file operations consume 2-5k tokens per file.
@@ -619,7 +619,6 @@ O artefato deve:
619
619
  5. MUST generate database-schema.md using template
620
620
  6. MUST include table catalog with all columns
621
621
  7. MUST return structured output to CLI (workflow state managed via PostgreSQL)
622
- 8. MUST create checkpoint after MAP phase fully complete (with api-extractor)
623
622
 
624
623
  ### MUST NOT (Forbidden)
625
624
 
@@ -754,7 +753,6 @@ Before claiming phase complete, MUST provide evidence:
754
753
  - [ ] Query patterns analyzed (if applicable)
755
754
  - [ ] Artifact saved to correct path
756
755
  - [ ] Structured output returned to CLI
757
- - [ ] Checkpoint created (after MAP phase complete)
758
756
 
759
757
  **FORBIDDEN**: Claiming completion without valid Mermaid ER diagram.
760
758
 
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: specifier
3
3
  description: Agente Especificador que transforma requests de features em especificacoes tecnicas detalhadas. Use quando precisar criar spec.md para uma nova feature ou requisito.
4
- tools: Read, Write, Edit, Grep, Glob, WebSearch, mcp__orchestrator-extended__artifactStore, mcp__orchestrator-tools__startAgentInvocation, mcp__orchestrator-tools__completeAgentInvocation
4
+ tools: Read, Write, Edit, Grep, Glob, WebSearch, Agent, mcp__orchestrator-extended__artifactStore, mcp__orchestrator-extended__artifactRetrieve, mcp__orchestrator-tools__startAgentInvocation, mcp__orchestrator-tools__completeAgentInvocation
5
5
  model: sonnet
6
6
  color: red
7
7
  permissionMode: default
@@ -29,7 +29,6 @@ Sua funcao e transformar requests de features em especificacoes tecnicas detalha
29
29
 
30
30
  ### MCP Tools
31
31
  - `searchPerplexity(query)`: Pesquisa tecnica atualizada
32
- - `lookupKnowledgeBase(topic)`: Busca em docs do projeto
33
32
 
34
33
  ### Skills
35
34
  - `kb-lookup`: Busca na CONSTITUTION.md e docs
@@ -50,9 +49,9 @@ Sua funcao e transformar requests de features em especificacoes tecnicas detalha
50
49
  ### 2. Coleta de Contexto
51
50
 
52
51
  ```
53
- 1. Se codebase indexado (GraphRAG):
54
- - MUST call graphragEnrichContext with includeCodeExamples: true
55
- - Discover relevant code examples from the codebase
52
+ 1. Busque exemplos de codigo relevantes no codebase:
53
+ - Use Grep/Glob para achar implementacoes semelhantes
54
+ - Se forem mais de 3 arquivos: despache Explore (3-File Rule, abaixo)
56
55
  - Include reference files in specification
57
56
  2. Se research-context existe:
58
57
  - Incorpore findings relevantes
@@ -226,7 +225,7 @@ Response: { ... }
226
225
 
227
226
  The following codebase files implement relevant patterns:
228
227
 
229
- {If codebase is indexed via GraphRAG, list code examples grouped by type:}
228
+ {If Grep/Glob or Explore found code examples, list them grouped by type:}
230
229
 
231
230
  ### Similar Features
232
231
  - `path/to/similar-feature.ts` - Demonstrates X pattern
@@ -235,7 +234,7 @@ The following codebase files implement relevant patterns:
235
234
  ### Architecture Patterns
236
235
  - `path/to/architecture-pattern.ts` - Reference architecture
237
236
 
238
- {Note: This section is only included when GraphRAG discovers relevant code examples. If codebase is not indexed, omit this section.}
237
+ {Note: Omit this section only when no search (Grep/Glob or Explore) found relevant code examples.}
239
238
 
240
239
  **Implementer should reference these files for architectural consistency.**
241
240
 
@@ -300,7 +299,7 @@ O artefato deve:
300
299
  Before reading/editing files directly:
301
300
 
302
301
  1. Estimate how many files you'll need to access
303
- 2. If MORE than 3 files: MUST use Task tool to dispatch Explore agent
302
+ 2. If MORE than 3 files: MUST use the `Agent` tool with `subagent_type: "Explore"` (read-only) to gather focused context. Dispatch no other type: never an agent that commits (any agent granted `createCheckpoint`; the list is `COMMITTING_AGENTS` in the `pre-agent-dispatch` hook) and never `orchestrator`.
304
303
  3. If 3 or fewer files: MAY operate directly
305
304
 
306
305
  Rationale: Direct file operations consume 2-5k tokens per file.
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: task-generator
3
3
  description: Agente Gerador de Tarefas que transforma planos tecnicos em backlogs de tarefas atomicas. Use quando precisar criar tasks.md a partir de um plano aprovado.
4
- tools: Read, Write, Edit, Grep, Glob, mcp__orchestrator-extended__artifactStore, mcp__orchestrator-extended__artifactRetrieve, mcp__orchestrator-tools__startAgentInvocation, mcp__orchestrator-tools__completeAgentInvocation
4
+ tools: Read, Write, Edit, Grep, Glob, Agent, mcp__orchestrator-extended__artifactStore, mcp__orchestrator-extended__artifactRetrieve, mcp__orchestrator-tools__startAgentInvocation, mcp__orchestrator-tools__completeAgentInvocation
5
5
  model: sonnet
6
6
  color: pink
7
7
  permissionMode: default
@@ -29,7 +29,6 @@ Sua funcao e transformar planos tecnicos em backlogs de tarefas atomicas e execu
29
29
  ## Ferramentas Disponiveis
30
30
 
31
31
  ### MCP Tools
32
- - `lookupKnowledgeBase(topic)`: Busca convencoes e patterns
33
32
  - `detectParallelization(tasks)`: Analisa oportunidades de paralelismo
34
33
 
35
34
  ### Skills
@@ -326,7 +325,7 @@ O artefato deve:
326
325
  Before reading/editing files directly:
327
326
 
328
327
  1. Estimate how many files you'll need to access
329
- 2. If MORE than 3 files: MUST use Task tool to dispatch Explore agent
328
+ 2. If MORE than 3 files: MUST use the `Agent` tool with `subagent_type: "Explore"` (read-only) to gather focused context. Dispatch no other type: never an agent that commits (any agent granted `createCheckpoint`; the list is `COMMITTING_AGENTS` in the `pre-agent-dispatch` hook) and never `orchestrator`.
330
329
  3. If 3 or fewer files: MAY operate directly
331
330
 
332
331
  Rationale: Direct file operations consume 2-5k tokens per file.
@@ -22,7 +22,6 @@ You are a focused test execution sidecar running alongside implementer panes, pr
22
22
 
23
23
  Stack-specific commands, conventions, and patterns are provided in the [Stack Context]
24
24
  section of your task prompt. Use those commands instead of hardcoded defaults.
25
- If you need deeper guidance: `getStackConventions({pack: "<from-context>", topic: "..."})`
26
25
 
27
26
  ## Responsibilities
28
27
 
@@ -7139,11 +7139,27 @@ function outputBlock(reason) {
7139
7139
  import { createReadStream, existsSync as existsSync2 } from "node:fs";
7140
7140
  import { createInterface } from "node:readline";
7141
7141
  var MODEL_PRICING = {
7142
- "claude-sonnet-4-6": { input: 3, output: 15, cacheRead: 0.3 },
7143
- "claude-opus-4-6": { input: 15, output: 75, cacheRead: 1.5 },
7144
- "claude-haiku-4-6": { input: 0.25, output: 1.25, cacheRead: 0.03 }
7142
+ "claude-opus-5-5": { input: 4, output: 20, cacheRead: 0.2 },
7143
+ "claude-fable-5-1": { input: 10, output: 50, cacheRead: 0.25 },
7144
+ "claude-opus-5": { input: 5, output: 25, cacheRead: 0.5 },
7145
+ "claude-sonnet-5": { input: 2, output: 10, cacheRead: 0.2 },
7146
+ "claude-haiku-4-5": { input: 1, output: 5, cacheRead: 0.1 },
7147
+ "claude-opus-4-6": { input: 5, output: 25, cacheRead: 0.5 },
7148
+ "claude-sonnet-4-6": { input: 3, output: 15, cacheRead: 0.3 }
7145
7149
  };
7146
7150
  var DEFAULT_MODEL = "claude-sonnet-4-6";
7151
+ var SYNTHETIC_MODEL = "<synthetic>";
7152
+ function pickSessionModel(stats) {
7153
+ let best;
7154
+ let bestStat;
7155
+ for (const [model, stat] of stats) {
7156
+ if (!bestStat || stat.count > bestStat.count || stat.count === bestStat.count && stat.lastSeen > bestStat.lastSeen) {
7157
+ best = model;
7158
+ bestStat = stat;
7159
+ }
7160
+ }
7161
+ return best;
7162
+ }
7147
7163
  async function parseJsonlSession(filePath) {
7148
7164
  try {
7149
7165
  if (!existsSync2(filePath)) return null;
@@ -7154,6 +7170,8 @@ async function parseJsonlSession(filePath) {
7154
7170
  let firstTimestamp = null;
7155
7171
  let lastTimestamp = null;
7156
7172
  let hasData = false;
7173
+ const modelStats = /* @__PURE__ */ new Map();
7174
+ let assistantIndex = 0;
7157
7175
  const rl = createInterface({
7158
7176
  input: createReadStream(filePath, { encoding: "utf-8" }),
7159
7177
  crlfDelay: Infinity
@@ -7178,6 +7196,15 @@ async function parseJsonlSession(filePath) {
7178
7196
  } catch {
7179
7197
  }
7180
7198
  }
7199
+ const lineModel = parsed.message?.model;
7200
+ if (parsed.type === "assistant" && typeof lineModel === "string" && lineModel !== "" && lineModel !== SYNTHETIC_MODEL) {
7201
+ assistantIndex += 1;
7202
+ const stat = modelStats.get(lineModel);
7203
+ modelStats.set(lineModel, {
7204
+ count: (stat?.count ?? 0) + 1,
7205
+ lastSeen: assistantIndex
7206
+ });
7207
+ }
7181
7208
  if (parsed.type === "tool_use") {
7182
7209
  toolCalls += 1;
7183
7210
  } else if (parsed.message?.content) {
@@ -7194,12 +7221,14 @@ async function parseJsonlSession(filePath) {
7194
7221
  }
7195
7222
  if (!hasData) return null;
7196
7223
  const durationSeconds = firstTimestamp !== null && lastTimestamp !== null ? Math.round((lastTimestamp - firstTimestamp) / 1e3) : 0;
7224
+ const model = pickSessionModel(modelStats);
7197
7225
  return {
7198
7226
  inputTokens,
7199
7227
  outputTokens,
7200
7228
  cacheReadTokens,
7201
7229
  toolCalls,
7202
- durationSeconds
7230
+ durationSeconds,
7231
+ ...model !== void 0 ? { model } : {}
7203
7232
  };
7204
7233
  } catch {
7205
7234
  return null;
@@ -7228,7 +7257,11 @@ async function findJsonlPath(sessionId) {
7228
7257
  }
7229
7258
  function estimateCost(metrics, model) {
7230
7259
  try {
7231
- const pricing = MODEL_PRICING[model ?? DEFAULT_MODEL] ?? MODEL_PRICING[DEFAULT_MODEL];
7260
+ const requested = model ?? metrics.model ?? DEFAULT_MODEL;
7261
+ const pricing = Object.prototype.hasOwnProperty.call(
7262
+ MODEL_PRICING,
7263
+ requested
7264
+ ) ? MODEL_PRICING[requested] : MODEL_PRICING[DEFAULT_MODEL];
7232
7265
  if (!pricing) return 0;
7233
7266
  const inputCost = metrics.inputTokens / 1e6 * pricing.input;
7234
7267
  const outputCost = metrics.outputTokens / 1e6 * pricing.output;
@@ -7282,6 +7315,9 @@ function buildTelemetryPayload(sessionId, metrics, estimatedCostUsd, model, vari
7282
7315
  ...variant ? { variant } : {}
7283
7316
  };
7284
7317
  }
7318
+ function estimateSessionCost(metrics) {
7319
+ return estimateCost(metrics, metrics.model);
7320
+ }
7285
7321
  async function emitFinopsTelemetry(stdin, workflowId, variant) {
7286
7322
  const sessionId = getField(stdin, "session_id");
7287
7323
  if (!sessionId) {
@@ -7298,7 +7334,7 @@ async function emitFinopsTelemetry(stdin, workflowId, variant) {
7298
7334
  log(HOOK, "[FINOPS] Failed to parse JSONL, skipping telemetry");
7299
7335
  return;
7300
7336
  }
7301
- const cost = estimateCost(metrics);
7337
+ const cost = estimateSessionCost(metrics);
7302
7338
  const output = formatFinopsOutput(metrics, cost);
7303
7339
  console.error(output);
7304
7340
  if (shouldPostTelemetry(workflowId, metrics)) {
@@ -7360,7 +7396,7 @@ function decideDanglingAction(input) {
7360
7396
  if (agentInvocationState === "stale") {
7361
7397
  return {
7362
7398
  action: "block",
7363
- reason: `[DANGLING-GUARD] Workflow ${workflow.id} has a STALE agent invocation (agent likely crashed/died) and is still ${workflow.status}. You must call mcp__orchestrator-extended__completeWorkflow({ workflowId: '${workflow.id}' }) before ending the session.`
7399
+ reason: `[DANGLING-GUARD] Workflow ${workflow.id} has a STALE agent invocation (its last agent looks dead) and is still ${workflow.status}. Confirm with getStatus before acting: if the work is finished, close it with completeWorkflow (orchestrator-extended server), workflowId '${workflow.id}'; if not, re-dispatch the phase agent or tell the user why it stays open.`
7364
7400
  };
7365
7401
  }
7366
7402
  if (hasPendingAction || workflow.status === "awaiting_approval") {
@@ -7371,7 +7407,7 @@ function decideDanglingAction(input) {
7371
7407
  }
7372
7408
  return {
7373
7409
  action: "block",
7374
- reason: `[DANGLING-GUARD] Workflow ${workflow.id} is still ${workflow.status} (phase: ${workflow.phase}). You must call mcp__orchestrator-extended__completeWorkflow({ workflowId: '${workflow.id}' }) before ending the session.`
7410
+ reason: `[DANGLING-GUARD] Workflow ${workflow.id} is still ${workflow.status} (phase: ${workflow.phase}) with no agent running. If its work is finished, close it with completeWorkflow (orchestrator-extended server), workflowId '${workflow.id}'. If it is paused on purpose (tasks still pending, a gate waiting on the user, or work owned by another terminal), do NOT complete it: tell the user it stays open and why, then end the turn.`
7375
7411
  };
7376
7412
  }
7377
7413
  function summarizeAgentInvocations(invocations) {
@@ -7451,6 +7487,7 @@ main().catch(() => process.exit(0));
7451
7487
  export {
7452
7488
  buildTelemetryPayload,
7453
7489
  decideDanglingAction,
7490
+ estimateSessionCost,
7454
7491
  shouldPostTelemetry,
7455
7492
  summarizeAgentInvocations
7456
7493
  };
@@ -69,6 +69,14 @@ export function buildTelemetryPayload(
69
69
  };
70
70
  }
71
71
 
72
+ /**
73
+ * Session cost priced at the model read from the JSONL (metrics.model, D-254);
74
+ * an absent or unpriced model falls back to DEFAULT_MODEL inside estimateCost.
75
+ */
76
+ export function estimateSessionCost(metrics: SessionMetrics): number {
77
+ return estimateCost(metrics, metrics.model);
78
+ }
79
+
72
80
  /**
73
81
  * FinOpsAI telemetry block — fail-open, 5s timeout.
74
82
  * Parses session JSONL, emits cost/token metrics, and POSTs to API if workflow active.
@@ -96,7 +104,7 @@ async function emitFinopsTelemetry(
96
104
  return;
97
105
  }
98
106
 
99
- const cost = estimateCost(metrics);
107
+ const cost = estimateSessionCost(metrics);
100
108
  const output = formatFinopsOutput(metrics, cost);
101
109
  console.error(output);
102
110
 
@@ -268,7 +276,7 @@ export function decideDanglingAction(input: DanglingDecisionInput): DanglingDeci
268
276
  if (agentInvocationState === 'stale') {
269
277
  return {
270
278
  action: 'block',
271
- reason: `[DANGLING-GUARD] Workflow ${workflow.id} has a STALE agent invocation (agent likely crashed/died) and is still ${workflow.status}. You must call mcp__orchestrator-extended__completeWorkflow({ workflowId: '${workflow.id}' }) before ending the session.`,
279
+ reason: `[DANGLING-GUARD] Workflow ${workflow.id} has a STALE agent invocation (its last agent looks dead) and is still ${workflow.status}. Confirm with getStatus before acting: if the work is finished, close it with completeWorkflow (orchestrator-extended server), workflowId '${workflow.id}'; if not, re-dispatch the phase agent or tell the user why it stays open.`,
272
280
  };
273
281
  }
274
282
 
@@ -286,7 +294,7 @@ export function decideDanglingAction(input: DanglingDecisionInput): DanglingDeci
286
294
  // human-wait. This is the case the guard exists for.
287
295
  return {
288
296
  action: 'block',
289
- reason: `[DANGLING-GUARD] Workflow ${workflow.id} is still ${workflow.status} (phase: ${workflow.phase}). You must call mcp__orchestrator-extended__completeWorkflow({ workflowId: '${workflow.id}' }) before ending the session.`,
297
+ reason: `[DANGLING-GUARD] Workflow ${workflow.id} is still ${workflow.status} (phase: ${workflow.phase}) with no agent running. If its work is finished, close it with completeWorkflow (orchestrator-extended server), workflowId '${workflow.id}'. If it is paused on purpose (tasks still pending, a gate waiting on the user, or work owned by another terminal), do NOT complete it: tell the user it stays open and why, then end the turn.`,
290
298
  };
291
299
  }
292
300
 
@@ -18,6 +18,12 @@ export interface SessionMetrics {
18
18
  cacheReadTokens: number;
19
19
  toolCalls: number;
20
20
  durationSeconds: number;
21
+ /**
22
+ * Session model read from the JSONL (message.model of assistant lines):
23
+ * the most frequent real model, a tie going to the one seen last.
24
+ * '<synthetic>' is ignored. Absent when no real model was found.
25
+ */
26
+ model?: string;
21
27
  }
22
28
 
23
29
  export interface ModelPricing {
@@ -31,12 +37,22 @@ export interface ModelPricing {
31
37
 
32
38
  // ─── Constants ────────────────────────────────────────────
33
39
 
40
+ // Source: live pricing page https://platform.claude.com/docs/en/about-claude/pricing.md,
41
+ // fetched 2026-09-27 (S536, D-254); "Cache hits and refreshes" column as cacheRead
42
+ // (0.025x input on Fable 5.1, 0.05x on Opus 5.5, 0.1x on the others).
43
+ // It confirmed, model by model, the claude-api skill snapshot 2026-06-24 (Claude Code 2.1.283).
34
44
  export const MODEL_PRICING: Record<string, ModelPricing> = {
45
+ 'claude-opus-5-5': { input: 4.0, output: 20.0, cacheRead: 0.2 },
46
+ 'claude-fable-5-1': { input: 10.0, output: 50.0, cacheRead: 0.25 },
47
+ 'claude-opus-5': { input: 5.0, output: 25.0, cacheRead: 0.5 },
48
+ 'claude-sonnet-5': { input: 2.0, output: 10.0, cacheRead: 0.2 },
49
+ 'claude-haiku-4-5': { input: 1.0, output: 5.0, cacheRead: 0.1 },
50
+ 'claude-opus-4-6': { input: 5.0, output: 25.0, cacheRead: 0.5 },
35
51
  'claude-sonnet-4-6': { input: 3.0, output: 15.0, cacheRead: 0.3 },
36
- 'claude-opus-4-6': { input: 15.0, output: 75.0, cacheRead: 1.5 },
37
- 'claude-haiku-4-6': { input: 0.25, output: 1.25, cacheRead: 0.03 },
38
52
  };
39
53
 
54
+ /** Fallback only: used when the JSONL carries no model or an unpriced one. */
55
+
40
56
  export const DEFAULT_MODEL = 'claude-sonnet-4-6';
41
57
 
42
58
  // ─── JSONL Parsing ────────────────────────────────────────
@@ -55,12 +71,41 @@ interface JsonlLine {
55
71
  type?: string;
56
72
  usage?: UsageBlock;
57
73
  message?: {
74
+ model?: unknown;
58
75
  usage?: UsageBlock;
59
76
  content?: ContentBlock[];
60
77
  };
61
78
  timestamp?: string;
62
79
  }
63
80
 
81
+ /** Model name Claude Code writes on locally generated assistant lines (zero usage). */
82
+ const SYNTHETIC_MODEL = '<synthetic>';
83
+
84
+ interface ModelStat {
85
+ count: number;
86
+ /** Ordinal of the model's last assistant line in the file. */
87
+ lastSeen: number;
88
+ }
89
+
90
+ /**
91
+ * Most frequent model; among tied models, the one whose last line comes latest.
92
+ */
93
+ function pickSessionModel(stats: Map<string, ModelStat>): string | undefined {
94
+ let best: string | undefined;
95
+ let bestStat: ModelStat | undefined;
96
+ for (const [model, stat] of stats) {
97
+ if (
98
+ !bestStat ||
99
+ stat.count > bestStat.count ||
100
+ (stat.count === bestStat.count && stat.lastSeen > bestStat.lastSeen)
101
+ ) {
102
+ best = model;
103
+ bestStat = stat;
104
+ }
105
+ }
106
+ return best;
107
+ }
108
+
64
109
  /**
65
110
  * Parse a Claude Code session JSONL file using streaming readline.
66
111
  * Aggregates token usage, tool call count, and duration from timestamps.
@@ -78,6 +123,8 @@ export async function parseJsonlSession(filePath: string): Promise<SessionMetric
78
123
  let firstTimestamp: number | null = null;
79
124
  let lastTimestamp: number | null = null;
80
125
  let hasData = false;
126
+ const modelStats = new Map<string, ModelStat>();
127
+ let assistantIndex = 0;
81
128
 
82
129
  const rl = createInterface({
83
130
  input: createReadStream(filePath, { encoding: 'utf-8' }),
@@ -111,6 +158,22 @@ export async function parseJsonlSession(filePath: string): Promise<SessionMetric
111
158
  }
112
159
  }
113
160
 
161
+ // Session model: only assistant lines carry the model that was billed.
162
+ const lineModel = parsed.message?.model;
163
+ if (
164
+ parsed.type === 'assistant' &&
165
+ typeof lineModel === 'string' &&
166
+ lineModel !== '' &&
167
+ lineModel !== SYNTHETIC_MODEL
168
+ ) {
169
+ assistantIndex += 1;
170
+ const stat = modelStats.get(lineModel);
171
+ modelStats.set(lineModel, {
172
+ count: (stat?.count ?? 0) + 1,
173
+ lastSeen: assistantIndex,
174
+ });
175
+ }
176
+
114
177
  // Count tool_use events — in Claude Code JSONL, tool_use is nested
115
178
  // inside message.content[] blocks, not at the top-level type field.
116
179
  if (parsed.type === 'tool_use') {
@@ -139,12 +202,15 @@ export async function parseJsonlSession(filePath: string): Promise<SessionMetric
139
202
  ? Math.round((lastTimestamp - firstTimestamp) / 1000)
140
203
  : 0;
141
204
 
205
+ const model = pickSessionModel(modelStats);
206
+
142
207
  return {
143
208
  inputTokens,
144
209
  outputTokens,
145
210
  cacheReadTokens,
146
211
  toolCalls,
147
212
  durationSeconds,
213
+ ...(model !== undefined ? { model } : {}),
148
214
  };
149
215
  } catch {
150
216
  return null;
@@ -187,12 +253,19 @@ export async function findJsonlPath(sessionId: string): Promise<string | null> {
187
253
 
188
254
  /**
189
255
  * Estimate API cost from session metrics using model pricing table.
256
+ * Model precedence: explicit `model` argument, then `metrics.model`, then DEFAULT_MODEL.
190
257
  * Falls back to DEFAULT_MODEL pricing for unknown models.
191
258
  * Never throws (fail-open).
192
259
  */
193
260
  export function estimateCost(metrics: SessionMetrics, model?: string): number {
194
261
  try {
195
- const pricing = MODEL_PRICING[model ?? DEFAULT_MODEL] ?? MODEL_PRICING[DEFAULT_MODEL];
262
+ const requested = model ?? metrics.model ?? DEFAULT_MODEL;
263
+ const pricing: ModelPricing | undefined = Object.prototype.hasOwnProperty.call(
264
+ MODEL_PRICING,
265
+ requested
266
+ )
267
+ ? MODEL_PRICING[requested]
268
+ : MODEL_PRICING[DEFAULT_MODEL];
196
269
  if (!pricing) return 0;
197
270
 
198
271
  const inputCost = (metrics.inputTokens / 1_000_000) * pricing.input;