@orchestrator-claude/definitions 3.40.0 → 3.42.1
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.
- package/agents/api-extractor.md +1 -2
- package/agents/azure-devops-engineer.md +5 -10
- package/agents/business-rule-miner.md +0 -2
- package/agents/cicd-platform-engineer.md +5 -5
- package/agents/code-archaeologist.md +0 -2
- package/agents/docs-guardian.md +10 -16
- package/agents/frontend-engineer.md +5 -4
- package/agents/implementer.md +2 -7
- package/agents/legacy-synthesizer.md +2 -2
- package/agents/methodology-researcher.md +15 -15
- package/agents/operational-artifact-builder.md +4 -2
- package/agents/planner.md +5 -5
- package/agents/presentation-engineer.md +5 -9
- package/agents/python-data-engineer.md +6 -9
- package/agents/researcher.md +16 -16
- package/agents/reviewer.md +3 -5
- package/agents/schema-extractor.md +0 -2
- package/agents/specifier.md +7 -8
- package/agents/task-generator.md +2 -3
- package/agents/test-sidecar.md +0 -1
- package/hooks/dangling-guard.mjs +45 -8
- package/hooks/dangling-guard.ts +11 -3
- package/hooks/lib/finops-parser.ts +76 -3
- package/manifest.json +25 -25
- package/package.json +1 -1
- package/skills/brainstorm-formalize/SKILL.md +45 -4
- package/skills/design-system-validated-loop/SKILL.md +4 -4
- package/skills/release/release.sh +53 -7
package/agents/reviewer.md
CHANGED
|
@@ -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
|
-
- `
|
|
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
|
|
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
|
|
package/agents/specifier.md
CHANGED
|
@@ -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.
|
|
54
|
-
-
|
|
55
|
-
-
|
|
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
|
|
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:
|
|
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
|
|
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.
|
package/agents/task-generator.md
CHANGED
|
@@ -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
|
|
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.
|
package/agents/test-sidecar.md
CHANGED
|
@@ -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
|
|
package/hooks/dangling-guard.mjs
CHANGED
|
@@ -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-
|
|
7143
|
-
"claude-
|
|
7144
|
-
"claude-
|
|
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
|
|
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 =
|
|
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
|
|
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}).
|
|
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
|
};
|
package/hooks/dangling-guard.ts
CHANGED
|
@@ -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 =
|
|
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
|
|
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}).
|
|
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
|
|
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;
|