@xhulz/nina 0.32.0 → 0.34.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.
- package/README.md +9 -5
- package/package.json +1 -1
- package/releases/0.33.0/core/GAPS.md +125 -0
- package/releases/0.33.0/core/answered.json +29 -0
- package/releases/0.33.0/core/defaults/tree/.claude/agents/architect.md +1 -0
- package/releases/0.33.0/core/defaults/tree/.claude/agents/dba.md +3 -0
- package/releases/0.33.0/core/defaults/tree/.claude/agents/devops.md +1 -0
- package/releases/0.33.0/core/defaults/tree/.claude/agents/implementer.md +1 -0
- package/releases/0.33.0/core/defaults/tree/.claude/agents/integration-tester.md +3 -0
- package/releases/0.33.0/core/defaults/tree/.claude/agents/qa.md +2 -0
- package/releases/0.33.0/core/defaults/tree/.claude/agents/reviewer.md +3 -0
- package/releases/0.33.0/core/defaults/tree/.claude/agents/secops.md +3 -0
- package/releases/0.33.0/core/defaults/tree/.claude/agents/solidity-auditor.md +3 -0
- package/releases/0.33.0/core/defaults/tree/.claude/agents/solidity-dev.md +3 -0
- package/releases/0.33.0/core/tree/.claude/agents/architect.md +127 -0
- package/releases/0.33.0/core/tree/.claude/agents/dba.md +110 -0
- package/releases/0.33.0/core/tree/.claude/agents/devops.md +114 -0
- package/releases/0.33.0/core/tree/.claude/agents/implementer.md +156 -0
- package/releases/0.33.0/core/tree/.claude/agents/integration-tester.md +168 -0
- package/releases/0.33.0/core/tree/.claude/agents/planner.md +132 -0
- package/releases/0.33.0/core/tree/.claude/agents/qa.md +148 -0
- package/releases/0.33.0/core/tree/.claude/agents/reviewer.md +183 -0
- package/releases/0.33.0/core/tree/.claude/agents/secops.md +121 -0
- package/releases/0.33.0/core/tree/.claude/agents/solidity-auditor.md +126 -0
- package/releases/0.33.0/core/tree/.claude/agents/solidity-dev.md +123 -0
- package/releases/0.33.0/core/tree/.claude/agents-overview.md +64 -0
- package/releases/0.33.0/core/tree/.claude/frontend.md +10 -0
- package/releases/0.33.0/core/tree/.claude/graph.md +75 -0
- package/releases/0.33.0/core/tree/.claude/patterns.md +213 -0
- package/releases/0.33.0/core/tree/.claude/pills/README.md +146 -0
- package/releases/0.33.0/core/tree/.claude/pipeline.md +101 -0
- package/releases/0.33.0/core/tree/.claude/retrieval.md +103 -0
- package/releases/0.33.0/core/tree/.claude/router.md +198 -0
- package/releases/0.33.0/core/tree/.claude/templates/integration.md +45 -0
- package/releases/0.33.0/core/tree/CLAUDE.md +161 -0
- package/releases/0.33.0/core/tree/scripts/cost-watch.mjs +18 -0
- package/releases/0.33.0/core/tree/scripts/edit-guard.mjs +16 -0
- package/releases/0.33.0/core/tree/scripts/harness-check.mjs +71 -0
- package/releases/0.33.0/core/tree/scripts/loop-gate.mjs +19 -0
- package/releases/0.33.0/core/vocabulary.json +13 -0
- package/releases/0.33.0/surfaces/blockchain/tree/.claude/agents/architect.md +5 -0
- package/releases/0.33.0/surfaces/blockchain/tree/.claude/agents/devops.md +5 -0
- package/releases/0.33.0/surfaces/blockchain/tree/.claude/agents/reviewer.md +5 -0
- package/releases/0.33.0/surfaces/blockchain/tree/.claude/agents/solidity-auditor.md +18 -0
- package/releases/0.33.0/surfaces/blockchain/tree/.claude/agents/solidity-dev.md +16 -0
- package/releases/0.33.0/surfaces/blockchain/tree/.claude/agents-overview.md +6 -0
- package/releases/0.33.0/surfaces/blockchain/tree/.claude/graph.md +11 -0
- package/releases/0.33.0/surfaces/blockchain/tree/.claude/router.md +6 -0
- package/releases/0.33.0/surfaces/blockchain/tree/CLAUDE.md +12 -0
- package/releases/0.33.0/surfaces/db/tree/.claude/agents/architect.md +5 -0
- package/releases/0.33.0/surfaces/db/tree/.claude/agents/dba.md +50 -0
- package/releases/0.33.0/surfaces/db/tree/.claude/agents/devops.md +5 -0
- package/releases/0.33.0/surfaces/db/tree/.claude/agents/implementer.md +2 -0
- package/releases/0.33.0/surfaces/db/tree/.claude/agents/integration-tester.md +10 -0
- package/releases/0.33.0/surfaces/db/tree/.claude/agents/planner.md +2 -0
- package/releases/0.33.0/surfaces/db/tree/.claude/agents/reviewer.md +22 -0
- package/releases/0.33.0/surfaces/db/tree/.claude/agents/secops.md +2 -0
- package/releases/0.33.0/surfaces/db/tree/.claude/agents-overview.md +5 -0
- package/releases/0.33.0/surfaces/db/tree/.claude/graph.md +8 -0
- package/releases/0.33.0/surfaces/db/tree/.claude/patterns.md +19 -0
- package/releases/0.33.0/surfaces/db/tree/.claude/pipeline.md +13 -0
- package/releases/0.33.0/surfaces/db/tree/.claude/retrieval.md +5 -0
- package/releases/0.33.0/surfaces/db/tree/.claude/router.md +4 -0
- package/releases/0.33.0/surfaces/db/tree/CLAUDE.md +17 -0
- package/releases/0.33.0/surfaces/edge-cf/tree/.claude/agents/architect.md +14 -0
- package/releases/0.33.0/surfaces/edge-cf/tree/.claude/agents/dba.md +2 -0
- package/releases/0.33.0/surfaces/edge-cf/tree/.claude/agents/devops.md +32 -0
- package/releases/0.33.0/surfaces/edge-cf/tree/.claude/agents/implementer.md +14 -0
- package/releases/0.33.0/surfaces/edge-cf/tree/.claude/agents/integration-tester.md +11 -0
- package/releases/0.33.0/surfaces/edge-cf/tree/.claude/agents/qa.md +3 -0
- package/releases/0.33.0/surfaces/edge-cf/tree/.claude/agents/reviewer.md +8 -0
- package/releases/0.33.0/surfaces/edge-cf/tree/.claude/agents/secops.md +8 -0
- package/releases/0.33.0/surfaces/edge-cf/tree/.claude/patterns.md +17 -0
- package/releases/0.33.0/surfaces/edge-cf/tree/.claude/retrieval.md +8 -0
- package/releases/0.33.0/surfaces/edge-cf/tree/CLAUDE.md +27 -0
- package/releases/0.33.0/surfaces/frontend/tree/.claude/agents/architect.md +20 -0
- package/releases/0.33.0/surfaces/frontend/tree/.claude/agents/devops.md +28 -0
- package/releases/0.33.0/surfaces/frontend/tree/.claude/agents/implementer.md +25 -0
- package/releases/0.33.0/surfaces/frontend/tree/.claude/agents/qa.md +17 -0
- package/releases/0.33.0/surfaces/frontend/tree/.claude/agents/reviewer.md +56 -0
- package/releases/0.33.0/surfaces/frontend/tree/.claude/patterns.md +8 -0
- package/releases/0.33.0/surfaces/frontend/tree/.claude/retrieval.md +2 -0
- package/releases/0.33.0/surfaces/frontend/tree/.claude/router.md +14 -0
- package/releases/0.33.0/surfaces/frontend/tree/CLAUDE.md +5 -0
- package/releases/0.33.0/surfaces/integrations/tree/.claude/agents/architect.md +16 -0
- package/releases/0.33.0/surfaces/integrations/tree/.claude/agents/implementer.md +12 -0
- package/releases/0.33.0/surfaces/integrations/tree/.claude/agents/integration-tester.md +20 -0
- package/releases/0.33.0/surfaces/integrations/tree/.claude/agents/planner.md +2 -0
- package/releases/0.33.0/surfaces/integrations/tree/.claude/agents/reviewer.md +21 -0
- package/releases/0.33.0/surfaces/integrations/tree/.claude/agents/secops.md +2 -0
- package/releases/0.33.0/surfaces/integrations/tree/.claude/agents-overview.md +8 -0
- package/releases/0.33.0/surfaces/integrations/tree/.claude/graph.md +10 -0
- package/releases/0.33.0/surfaces/integrations/tree/.claude/patterns.md +38 -0
- package/releases/0.33.0/surfaces/integrations/tree/.claude/pipeline.md +16 -0
- package/releases/0.33.0/surfaces/integrations/tree/.claude/retrieval.md +16 -0
- package/releases/0.33.0/surfaces/integrations/tree/.claude/router.md +6 -0
- package/releases/0.33.0/surfaces/integrations/tree/CLAUDE.md +17 -0
- package/releases/0.33.0/surfaces/money/tree/.claude/agents/architect.md +12 -0
- package/releases/0.33.0/surfaces/money/tree/.claude/agents/dba.md +14 -0
- package/releases/0.33.0/surfaces/money/tree/.claude/agents/implementer.md +11 -0
- package/releases/0.33.0/surfaces/money/tree/.claude/agents/integration-tester.md +3 -0
- package/releases/0.33.0/surfaces/money/tree/.claude/agents/reviewer.md +23 -0
- package/releases/0.33.0/surfaces/money/tree/.claude/agents/secops.md +5 -0
- package/releases/0.33.0/surfaces/money/tree/.claude/agents/solidity-auditor.md +4 -0
- package/releases/0.33.0/surfaces/money/tree/.claude/agents/solidity-dev.md +4 -0
- package/releases/0.33.0/surfaces/money/tree/.claude/graph.md +2 -0
- package/releases/0.33.0/surfaces/money/tree/.claude/patterns.md +49 -0
- package/releases/0.33.0/surfaces/money/tree/.claude/pipeline.md +2 -0
- package/releases/0.33.0/surfaces/money/tree/.claude/retrieval.md +2 -0
- package/releases/0.33.0/surfaces/money/tree/CLAUDE.md +11 -0
- package/releases/0.33.0/surfaces/pii/tree/.claude/agents/dba.md +2 -0
- package/releases/0.33.0/surfaces/pii/tree/.claude/agents/devops.md +8 -0
- package/releases/0.33.0/surfaces/pii/tree/.claude/agents/reviewer.md +8 -0
- package/releases/0.33.0/surfaces/pii/tree/.claude/agents/secops.md +2 -0
- package/releases/0.33.0/surfaces/pii/tree/.claude/agents/solidity-auditor.md +5 -0
- package/releases/0.33.0/surfaces/pii/tree/CLAUDE.md +5 -0
- package/releases/0.33.0/surfaces/prisma/tree/.claude/agents/architect.md +2 -0
- package/releases/0.33.0/surfaces/prisma/tree/.claude/agents/dba.md +39 -0
- package/releases/0.33.0/surfaces/prisma/tree/.claude/agents/devops.md +11 -0
- package/releases/0.33.0/surfaces/prisma/tree/.claude/agents/implementer.md +2 -0
- package/releases/0.33.0/surfaces/prisma/tree/.claude/agents/integration-tester.md +2 -0
- package/releases/0.33.0/surfaces/prisma/tree/.claude/agents/reviewer.md +2 -0
- package/releases/0.33.0/surfaces/prisma/tree/.claude/patterns.md +15 -0
- package/releases/0.33.0/surfaces/prisma/tree/CLAUDE.md +5 -0
- package/releases/0.34.0/core/GAPS.md +125 -0
- package/releases/0.34.0/core/answered.json +29 -0
- package/releases/0.34.0/core/defaults/tree/.claude/agents/architect.md +1 -0
- package/releases/0.34.0/core/defaults/tree/.claude/agents/dba.md +3 -0
- package/releases/0.34.0/core/defaults/tree/.claude/agents/devops.md +1 -0
- package/releases/0.34.0/core/defaults/tree/.claude/agents/implementer.md +1 -0
- package/releases/0.34.0/core/defaults/tree/.claude/agents/integration-tester.md +3 -0
- package/releases/0.34.0/core/defaults/tree/.claude/agents/qa.md +2 -0
- package/releases/0.34.0/core/defaults/tree/.claude/agents/reviewer.md +3 -0
- package/releases/0.34.0/core/defaults/tree/.claude/agents/secops.md +3 -0
- package/releases/0.34.0/core/defaults/tree/.claude/agents/solidity-auditor.md +3 -0
- package/releases/0.34.0/core/defaults/tree/.claude/agents/solidity-dev.md +3 -0
- package/releases/0.34.0/core/tree/.claude/agents/architect.md +127 -0
- package/releases/0.34.0/core/tree/.claude/agents/dba.md +110 -0
- package/releases/0.34.0/core/tree/.claude/agents/devops.md +114 -0
- package/releases/0.34.0/core/tree/.claude/agents/implementer.md +156 -0
- package/releases/0.34.0/core/tree/.claude/agents/integration-tester.md +168 -0
- package/releases/0.34.0/core/tree/.claude/agents/planner.md +132 -0
- package/releases/0.34.0/core/tree/.claude/agents/qa.md +148 -0
- package/releases/0.34.0/core/tree/.claude/agents/reviewer.md +183 -0
- package/releases/0.34.0/core/tree/.claude/agents/secops.md +121 -0
- package/releases/0.34.0/core/tree/.claude/agents/solidity-auditor.md +126 -0
- package/releases/0.34.0/core/tree/.claude/agents/solidity-dev.md +123 -0
- package/releases/0.34.0/core/tree/.claude/agents-overview.md +64 -0
- package/releases/0.34.0/core/tree/.claude/frontend.md +10 -0
- package/releases/0.34.0/core/tree/.claude/graph.md +75 -0
- package/releases/0.34.0/core/tree/.claude/patterns.md +213 -0
- package/releases/0.34.0/core/tree/.claude/pills/README.md +146 -0
- package/releases/0.34.0/core/tree/.claude/pipeline.md +101 -0
- package/releases/0.34.0/core/tree/.claude/retrieval.md +103 -0
- package/releases/0.34.0/core/tree/.claude/router.md +198 -0
- package/releases/0.34.0/core/tree/.claude/templates/integration.md +45 -0
- package/releases/0.34.0/core/tree/CLAUDE.md +161 -0
- package/releases/0.34.0/core/tree/scripts/cost-watch.mjs +18 -0
- package/releases/0.34.0/core/tree/scripts/edit-guard.mjs +16 -0
- package/releases/0.34.0/core/tree/scripts/harness-check.mjs +71 -0
- package/releases/0.34.0/core/tree/scripts/loop-gate.mjs +19 -0
- package/releases/0.34.0/core/vocabulary.json +13 -0
- package/releases/0.34.0/surfaces/blockchain/tree/.claude/agents/architect.md +5 -0
- package/releases/0.34.0/surfaces/blockchain/tree/.claude/agents/devops.md +5 -0
- package/releases/0.34.0/surfaces/blockchain/tree/.claude/agents/reviewer.md +5 -0
- package/releases/0.34.0/surfaces/blockchain/tree/.claude/agents/solidity-auditor.md +18 -0
- package/releases/0.34.0/surfaces/blockchain/tree/.claude/agents/solidity-dev.md +16 -0
- package/releases/0.34.0/surfaces/blockchain/tree/.claude/agents-overview.md +6 -0
- package/releases/0.34.0/surfaces/blockchain/tree/.claude/graph.md +11 -0
- package/releases/0.34.0/surfaces/blockchain/tree/.claude/router.md +6 -0
- package/releases/0.34.0/surfaces/blockchain/tree/CLAUDE.md +12 -0
- package/releases/0.34.0/surfaces/db/tree/.claude/agents/architect.md +5 -0
- package/releases/0.34.0/surfaces/db/tree/.claude/agents/dba.md +50 -0
- package/releases/0.34.0/surfaces/db/tree/.claude/agents/devops.md +5 -0
- package/releases/0.34.0/surfaces/db/tree/.claude/agents/implementer.md +2 -0
- package/releases/0.34.0/surfaces/db/tree/.claude/agents/integration-tester.md +10 -0
- package/releases/0.34.0/surfaces/db/tree/.claude/agents/planner.md +2 -0
- package/releases/0.34.0/surfaces/db/tree/.claude/agents/reviewer.md +22 -0
- package/releases/0.34.0/surfaces/db/tree/.claude/agents/secops.md +2 -0
- package/releases/0.34.0/surfaces/db/tree/.claude/agents-overview.md +5 -0
- package/releases/0.34.0/surfaces/db/tree/.claude/graph.md +8 -0
- package/releases/0.34.0/surfaces/db/tree/.claude/patterns.md +19 -0
- package/releases/0.34.0/surfaces/db/tree/.claude/pipeline.md +13 -0
- package/releases/0.34.0/surfaces/db/tree/.claude/retrieval.md +5 -0
- package/releases/0.34.0/surfaces/db/tree/.claude/router.md +4 -0
- package/releases/0.34.0/surfaces/db/tree/CLAUDE.md +17 -0
- package/releases/0.34.0/surfaces/edge-cf/tree/.claude/agents/architect.md +14 -0
- package/releases/0.34.0/surfaces/edge-cf/tree/.claude/agents/dba.md +2 -0
- package/releases/0.34.0/surfaces/edge-cf/tree/.claude/agents/devops.md +32 -0
- package/releases/0.34.0/surfaces/edge-cf/tree/.claude/agents/implementer.md +14 -0
- package/releases/0.34.0/surfaces/edge-cf/tree/.claude/agents/integration-tester.md +11 -0
- package/releases/0.34.0/surfaces/edge-cf/tree/.claude/agents/qa.md +3 -0
- package/releases/0.34.0/surfaces/edge-cf/tree/.claude/agents/reviewer.md +8 -0
- package/releases/0.34.0/surfaces/edge-cf/tree/.claude/agents/secops.md +8 -0
- package/releases/0.34.0/surfaces/edge-cf/tree/.claude/patterns.md +17 -0
- package/releases/0.34.0/surfaces/edge-cf/tree/.claude/retrieval.md +8 -0
- package/releases/0.34.0/surfaces/edge-cf/tree/CLAUDE.md +27 -0
- package/releases/0.34.0/surfaces/frontend/tree/.claude/agents/architect.md +20 -0
- package/releases/0.34.0/surfaces/frontend/tree/.claude/agents/devops.md +28 -0
- package/releases/0.34.0/surfaces/frontend/tree/.claude/agents/implementer.md +25 -0
- package/releases/0.34.0/surfaces/frontend/tree/.claude/agents/qa.md +17 -0
- package/releases/0.34.0/surfaces/frontend/tree/.claude/agents/reviewer.md +56 -0
- package/releases/0.34.0/surfaces/frontend/tree/.claude/patterns.md +8 -0
- package/releases/0.34.0/surfaces/frontend/tree/.claude/retrieval.md +2 -0
- package/releases/0.34.0/surfaces/frontend/tree/.claude/router.md +14 -0
- package/releases/0.34.0/surfaces/frontend/tree/CLAUDE.md +5 -0
- package/releases/0.34.0/surfaces/integrations/tree/.claude/agents/architect.md +16 -0
- package/releases/0.34.0/surfaces/integrations/tree/.claude/agents/implementer.md +12 -0
- package/releases/0.34.0/surfaces/integrations/tree/.claude/agents/integration-tester.md +20 -0
- package/releases/0.34.0/surfaces/integrations/tree/.claude/agents/planner.md +2 -0
- package/releases/0.34.0/surfaces/integrations/tree/.claude/agents/reviewer.md +21 -0
- package/releases/0.34.0/surfaces/integrations/tree/.claude/agents/secops.md +2 -0
- package/releases/0.34.0/surfaces/integrations/tree/.claude/agents-overview.md +8 -0
- package/releases/0.34.0/surfaces/integrations/tree/.claude/graph.md +10 -0
- package/releases/0.34.0/surfaces/integrations/tree/.claude/patterns.md +38 -0
- package/releases/0.34.0/surfaces/integrations/tree/.claude/pipeline.md +16 -0
- package/releases/0.34.0/surfaces/integrations/tree/.claude/retrieval.md +16 -0
- package/releases/0.34.0/surfaces/integrations/tree/.claude/router.md +6 -0
- package/releases/0.34.0/surfaces/integrations/tree/CLAUDE.md +17 -0
- package/releases/0.34.0/surfaces/money/tree/.claude/agents/architect.md +12 -0
- package/releases/0.34.0/surfaces/money/tree/.claude/agents/dba.md +14 -0
- package/releases/0.34.0/surfaces/money/tree/.claude/agents/implementer.md +11 -0
- package/releases/0.34.0/surfaces/money/tree/.claude/agents/integration-tester.md +3 -0
- package/releases/0.34.0/surfaces/money/tree/.claude/agents/reviewer.md +23 -0
- package/releases/0.34.0/surfaces/money/tree/.claude/agents/secops.md +5 -0
- package/releases/0.34.0/surfaces/money/tree/.claude/agents/solidity-auditor.md +4 -0
- package/releases/0.34.0/surfaces/money/tree/.claude/agents/solidity-dev.md +4 -0
- package/releases/0.34.0/surfaces/money/tree/.claude/graph.md +2 -0
- package/releases/0.34.0/surfaces/money/tree/.claude/patterns.md +49 -0
- package/releases/0.34.0/surfaces/money/tree/.claude/pipeline.md +2 -0
- package/releases/0.34.0/surfaces/money/tree/.claude/retrieval.md +2 -0
- package/releases/0.34.0/surfaces/money/tree/CLAUDE.md +11 -0
- package/releases/0.34.0/surfaces/pii/tree/.claude/agents/dba.md +2 -0
- package/releases/0.34.0/surfaces/pii/tree/.claude/agents/devops.md +8 -0
- package/releases/0.34.0/surfaces/pii/tree/.claude/agents/reviewer.md +8 -0
- package/releases/0.34.0/surfaces/pii/tree/.claude/agents/secops.md +2 -0
- package/releases/0.34.0/surfaces/pii/tree/.claude/agents/solidity-auditor.md +5 -0
- package/releases/0.34.0/surfaces/pii/tree/CLAUDE.md +5 -0
- package/releases/0.34.0/surfaces/prisma/tree/.claude/agents/architect.md +2 -0
- package/releases/0.34.0/surfaces/prisma/tree/.claude/agents/dba.md +39 -0
- package/releases/0.34.0/surfaces/prisma/tree/.claude/agents/devops.md +11 -0
- package/releases/0.34.0/surfaces/prisma/tree/.claude/agents/implementer.md +2 -0
- package/releases/0.34.0/surfaces/prisma/tree/.claude/agents/integration-tester.md +2 -0
- package/releases/0.34.0/surfaces/prisma/tree/.claude/agents/reviewer.md +2 -0
- package/releases/0.34.0/surfaces/prisma/tree/.claude/patterns.md +15 -0
- package/releases/0.34.0/surfaces/prisma/tree/CLAUDE.md +5 -0
- package/src/agentrun.mjs +2 -3
- package/src/commands/check.mjs +3 -1
- package/src/commands/export.mjs +9 -7
- package/src/commands/langfuse.mjs +25 -10
- package/src/commands/runs.mjs +7 -2
- package/src/commands/stats.mjs +37 -0
- package/src/commands/upgrade.mjs +12 -3
- package/src/expected.mjs +4 -3
- package/src/langfuse.mjs +31 -11
- package/src/transcripts.mjs +53 -1
package/src/commands/export.mjs
CHANGED
|
@@ -25,7 +25,7 @@ import { spawn } from 'node:child_process';
|
|
|
25
25
|
import { join } from 'node:path';
|
|
26
26
|
import { fileURLToPath } from 'node:url';
|
|
27
27
|
import { readRun, runFile } from '../agentrun.mjs';
|
|
28
|
-
import { BATCH, SETTLE_MINUTES, postScore, postScores, postSpans, scoreOf, settled, spanOf, spansOf } from '../langfuse.mjs';
|
|
28
|
+
import { BATCH, SETTLE_MINUTES, contextMode, postScore, postScores, postSpans, scoreOf, settled, spanOf, spansOf } from '../langfuse.mjs';
|
|
29
29
|
import { runOf, transcriptsOf } from '../transcripts.mjs';
|
|
30
30
|
import { exportsDir, snapshotsDir } from '../paths.mjs';
|
|
31
31
|
import { readConfig, statusPath, targetOf } from './langfuse.mjs';
|
|
@@ -214,7 +214,7 @@ export async function exportCommand(argv, ctx = {}) {
|
|
|
214
214
|
const release = await lock(only);
|
|
215
215
|
if (!release) return 0;
|
|
216
216
|
try {
|
|
217
|
-
const result = await exportProject({ slug: only, name: projectName(only), file: join(snapshotsDir(), `${only}.jsonl`), target, now: ctx.now, since: setting.since, quiet: true, content: setting
|
|
217
|
+
const result = await exportProject({ slug: only, name: projectName(only), file: join(snapshotsDir(), `${only}.jsonl`), target, now: ctx.now, since: setting.since, quiet: true, content: contextMode(setting) });
|
|
218
218
|
if (result.spans > 0 || result.scores > 0 || result.error) {
|
|
219
219
|
// The same failure again is not news: it was reported once, and repeating it every turn it is
|
|
220
220
|
// retried would be the noise the detectors exist to remove.
|
|
@@ -251,7 +251,7 @@ export async function exportCommand(argv, ctx = {}) {
|
|
|
251
251
|
continue;
|
|
252
252
|
}
|
|
253
253
|
try {
|
|
254
|
-
const content =
|
|
254
|
+
const content = contextMode(config.projects[slug]);
|
|
255
255
|
if ((await exportProject({ slug, name, file: join(dir, `${slug}.jsonl`), target, dry, now: ctx.now, content })).error) failed = true;
|
|
256
256
|
} finally {
|
|
257
257
|
await release();
|
|
@@ -265,7 +265,7 @@ export async function exportCommand(argv, ctx = {}) {
|
|
|
265
265
|
*
|
|
266
266
|
* @returns {Promise<{spans: number, scores: number, error: string|null}>} What went, and why the rest did not.
|
|
267
267
|
*/
|
|
268
|
-
async function exportProject({ slug, name, file, target, dry, now, since = '', quiet = false, content =
|
|
268
|
+
async function exportProject({ slug, name, file, target, dry, now, since = '', quiet = false, content = null }) {
|
|
269
269
|
const say = quiet ? () => {} : (line) => console.log(line);
|
|
270
270
|
const records = await recordsOf(file);
|
|
271
271
|
const sent = await sentOf(slug);
|
|
@@ -282,7 +282,8 @@ async function exportProject({ slug, name, file, target, dry, now, since = '', q
|
|
|
282
282
|
const tail = () => (notes.length ? ` — ${notes.join('; ')}` : '');
|
|
283
283
|
|
|
284
284
|
if (dry) {
|
|
285
|
-
|
|
285
|
+
const what = content === 'prompts' ? " with each stage's prompt and report" : content ? " with each stage's context" : '';
|
|
286
|
+
say(` ${name}: would send ${todo.spans.length} trace(s)${what} and ${todo.scores.length} score(s) to ${target.host}${tail()}`);
|
|
286
287
|
if (todo.spans[0]) say(` e.g. ${JSON.stringify(spanOf(todo.spans[0], name))}`);
|
|
287
288
|
return { spans: 0, scores: 0, error: null };
|
|
288
289
|
}
|
|
@@ -319,11 +320,12 @@ async function exportProject({ slug, name, file, target, dry, now, since = '', q
|
|
|
319
320
|
if (error) break;
|
|
320
321
|
const run = content ? await readRun(runFile(transcriptsOf(slug), record), record.round ?? 1) : null;
|
|
321
322
|
if (content && !run) bare += 1;
|
|
322
|
-
|
|
323
|
+
const sending = { prompts: content === 'prompts' };
|
|
324
|
+
let built = spansOf(record, name, run, {}, sending);
|
|
323
325
|
let size = JSON.stringify(built).length;
|
|
324
326
|
for (const limits of SMALLER) {
|
|
325
327
|
if (size <= MAX_BODY) break;
|
|
326
|
-
built = spansOf(record, name, run, limits);
|
|
328
|
+
built = spansOf(record, name, run, limits, sending);
|
|
327
329
|
size = JSON.stringify(built).length;
|
|
328
330
|
}
|
|
329
331
|
// Cutting its texts bounds each one, not how many calls a stage made. One that still does not fit
|
|
@@ -2,14 +2,15 @@
|
|
|
2
2
|
* `nina langfuse` — the keys, set once, and which projects send their runs to Langfuse on their own.
|
|
3
3
|
*
|
|
4
4
|
* nina langfuse login [--host <url>] ask for the keys, check them against Langfuse, keep them
|
|
5
|
-
* nina langfuse on [--project <dir>] [--content] send this project's runs after every turn
|
|
5
|
+
* nina langfuse on [--project <dir>] [--content | --prompts] send this project's runs after every turn
|
|
6
6
|
* nina langfuse off [--project <dir>]
|
|
7
7
|
* nina langfuse status
|
|
8
8
|
*
|
|
9
9
|
* Once a project is on, nobody runs an export: the `lessons` detector every composed project runs
|
|
10
10
|
* already snapshots the project each turn, and it starts the export in the background whenever a run
|
|
11
11
|
* is ready to go. What it sends is metadata unless the project is turned on with `--content`, which adds
|
|
12
|
-
* each stage's own context
|
|
12
|
+
* each stage's own context, or with `--prompts`, which adds only what passed between the agents: the
|
|
13
|
+
* prompt each stage was given and the report it handed back — see `src/langfuse.mjs`.
|
|
13
14
|
*
|
|
14
15
|
* The keys live in `~/.nina/exports/langfuse.json`, readable by the owner only, beside the record of what
|
|
15
16
|
* was sent; `LANGFUSE_PUBLIC_KEY`, `LANGFUSE_SECRET_KEY` and `LANGFUSE_HOST` override them. They are asked
|
|
@@ -21,20 +22,26 @@
|
|
|
21
22
|
import { chmod, mkdir, readFile, rename, rm, writeFile } from 'node:fs/promises';
|
|
22
23
|
import { join, resolve } from 'node:path';
|
|
23
24
|
import { createInterface } from 'node:readline';
|
|
24
|
-
import { DEFAULT_HOST, projectOf } from '../langfuse.mjs';
|
|
25
|
+
import { DEFAULT_HOST, contextMode, projectOf } from '../langfuse.mjs';
|
|
25
26
|
import { exportsDir, slugFor } from '../paths.mjs';
|
|
26
27
|
import { projectName } from './stats.mjs';
|
|
27
28
|
|
|
28
29
|
/** Where the keys and the switches live. */
|
|
29
30
|
export const configPath = () => join(exportsDir(), 'langfuse.json');
|
|
30
31
|
|
|
32
|
+
/** What a project sends, in words, by `contextMode`. */
|
|
33
|
+
const LABELS = { full: "with each stage's own context", prompts: "with each stage's prompt and report", none: 'as metadata only' };
|
|
34
|
+
|
|
35
|
+
/** The same, as `status` lists it. */
|
|
36
|
+
const SHORT = { full: 'with context', prompts: 'with prompts and reports', none: 'metadata only' };
|
|
37
|
+
|
|
31
38
|
/** Where the background export leaves what it last did, per project. */
|
|
32
39
|
export const statusPath = (slug) => join(exportsDir(), 'langfuse', `${slug}.status.json`);
|
|
33
40
|
|
|
34
41
|
/**
|
|
35
42
|
* The configuration on disk: keys, host, and the projects that are on.
|
|
36
43
|
*
|
|
37
|
-
* @returns {Promise<{publicKey?: string, secretKey?: string, host?: string, projects: Record<string, {content: boolean, since: string}>}>}
|
|
44
|
+
* @returns {Promise<{publicKey?: string, secretKey?: string, host?: string, projects: Record<string, {content: boolean|'prompts', since: string}>}>}
|
|
38
45
|
*/
|
|
39
46
|
export async function readConfig() {
|
|
40
47
|
try {
|
|
@@ -113,7 +120,7 @@ export async function langfuse(argv, ctx = {}) {
|
|
|
113
120
|
}
|
|
114
121
|
await writeConfig({ ...config, host, publicKey, secretKey });
|
|
115
122
|
console.log(` ✓ the keys open "${opened.project}" at ${host}; kept in ${configPath()}, readable by you only`);
|
|
116
|
-
console.log(' next, in each project that should send: `nina langfuse on` (add `--content` for each stage\'s
|
|
123
|
+
console.log(' next, in each project that should send: `nina langfuse on` (add `--prompts` for what passed between the agents, or `--content` for each stage\'s whole context)\n');
|
|
117
124
|
return 0;
|
|
118
125
|
}
|
|
119
126
|
|
|
@@ -123,13 +130,21 @@ export async function langfuse(argv, ctx = {}) {
|
|
|
123
130
|
console.error(' no keys yet — `nina langfuse login` first\n');
|
|
124
131
|
return 2;
|
|
125
132
|
}
|
|
126
|
-
|
|
133
|
+
if (argv.includes('--content') && argv.includes('--prompts')) {
|
|
134
|
+
console.error(' --content already sends the prompts; pass one of the two\n');
|
|
135
|
+
return 2;
|
|
136
|
+
}
|
|
137
|
+
const content = argv.includes('--content') ? true : argv.includes('--prompts') ? 'prompts' : false;
|
|
127
138
|
await writeConfig({ ...config, projects: { ...config.projects, [slug]: { content, since: new Date().toISOString() } } });
|
|
128
|
-
console.log(` ✓ ${project} sends its runs to ${target.host} from now on, after every turn, ${content
|
|
129
|
-
if (content) {
|
|
139
|
+
console.log(` ✓ ${project} sends its runs to ${target.host} from now on, after every turn, ${LABELS[contextMode({ content }) ?? 'none']}`);
|
|
140
|
+
if (content === true) {
|
|
130
141
|
console.log(" each stage's prompt, messages, tool calls and report go with it — code and whatever the stages read included;");
|
|
131
142
|
console.log(' obvious secrets are masked, and nothing more is: turn it on only where that code may leave this machine');
|
|
132
143
|
}
|
|
144
|
+
if (content === 'prompts') {
|
|
145
|
+
console.log(" each stage's prompt and report go with it, and nothing it read, wrote or called between them;");
|
|
146
|
+
console.log(' a prompt carries whatever the orchestrator put in it, diffs and code included, and only obvious secrets are masked');
|
|
147
|
+
}
|
|
133
148
|
console.log(` the runs from before now stay here; \`nina export --langfuse --project ${slug}\` sends them, once`);
|
|
134
149
|
console.log(' it runs from the `lessons` detector in this project\'s hooks — `nina check` says whether they are wired\n');
|
|
135
150
|
return 0;
|
|
@@ -150,12 +165,12 @@ export async function langfuse(argv, ctx = {}) {
|
|
|
150
165
|
for (const [name, setting] of on) {
|
|
151
166
|
const last = JSON.parse(await readFile(statusPath(name), 'utf8').catch(() => 'null'));
|
|
152
167
|
const said = !last ? 'nothing sent yet' : last.error ? `✗ ${last.at}: ${last.error}` : `last sent ${last.at}: ${last.spans} trace(s), ${last.scores} score(s)`;
|
|
153
|
-
console.log(` ${projectName(name)}: ${setting
|
|
168
|
+
console.log(` ${projectName(name)}: ${SHORT[contextMode(setting) ?? 'none']}, since ${setting.since.slice(0, 10)} — ${said}`);
|
|
154
169
|
}
|
|
155
170
|
console.log('');
|
|
156
171
|
return 0;
|
|
157
172
|
}
|
|
158
173
|
|
|
159
|
-
console.error(' usage: nina langfuse login [--host <url>] | on [--project <dir>] [--content] | off [--project <dir>] | status\n');
|
|
174
|
+
console.error(' usage: nina langfuse login [--host <url>] | on [--project <dir>] [--content | --prompts] | off [--project <dir>] | status\n');
|
|
160
175
|
return 2;
|
|
161
176
|
}
|
package/src/commands/runs.mjs
CHANGED
|
@@ -32,7 +32,7 @@ const ENDS = {
|
|
|
32
32
|
* stages first ran.
|
|
33
33
|
*
|
|
34
34
|
* @param {{runs: object[], end: string}} cycle
|
|
35
|
-
* @returns {{start: string, end: string, minutes: number|null, cost: number, unpriced: number, name: string, how: string, stages: {role: string, runs: number, rounds: number, back: number}[], files: number|null}}
|
|
35
|
+
* @returns {{start: string, end: string, minutes: number|null, cost: number, unpriced: number, name: string, how: string, stages: {role: string, runs: number, rounds: number, back: number}[], files: number|null, peak: number|null}}
|
|
36
36
|
*/
|
|
37
37
|
export function summarize(cycle) {
|
|
38
38
|
const rounds = cycle.runs;
|
|
@@ -56,6 +56,7 @@ export function summarize(cycle) {
|
|
|
56
56
|
stages.set(r.role, s);
|
|
57
57
|
}
|
|
58
58
|
const files = rounds.filter((r) => typeof r.files_touched === 'number').map((r) => r.files_touched);
|
|
59
|
+
const peaks = rounds.filter((r) => typeof r.context_peak === 'number').map((r) => r.context_peak);
|
|
59
60
|
return {
|
|
60
61
|
start,
|
|
61
62
|
end,
|
|
@@ -66,6 +67,7 @@ export function summarize(cycle) {
|
|
|
66
67
|
how: ENDS[cycle.end](rounds.at(-1)),
|
|
67
68
|
stages: [...stages.values()].map((s) => ({ ...s, runs: s.runs.size })),
|
|
68
69
|
files: files.length ? Math.max(...files) : null,
|
|
70
|
+
peak: peaks.length ? Math.max(...peaks) : null,
|
|
69
71
|
};
|
|
70
72
|
}
|
|
71
73
|
|
|
@@ -111,7 +113,10 @@ export async function runs(argv) {
|
|
|
111
113
|
const count = s.rounds > s.runs ? `${s.rounds} rounds` : `${s.rounds}`;
|
|
112
114
|
return `${pink(s.role)} ${count}${s.back ? amber(`, ${s.back} sent back`) : ''}`;
|
|
113
115
|
});
|
|
114
|
-
console.log(
|
|
116
|
+
console.log(
|
|
117
|
+
` ${chain.join(dim(' · '))}${c.files === null ? '' : dim(` · largest write ${c.files} file(s)`)}` +
|
|
118
|
+
(c.peak === null ? '' : dim(` · context up to ${Math.round(c.peak / 1000)}k`)),
|
|
119
|
+
);
|
|
115
120
|
}
|
|
116
121
|
const total = shown.reduce((a, c) => a + c.cost, 0);
|
|
117
122
|
const costliest = shown.reduce((a, c) => (c.cost > a.cost ? c : a), shown[0]);
|
package/src/commands/stats.mjs
CHANGED
|
@@ -223,6 +223,42 @@ function costReport(records) {
|
|
|
223
223
|
console.log(bold(` ${'all stages'.padEnd(20)}${String([...byRole.values()].flat().length).padStart(6)}${''.padStart(10)}${money(total).padStart(10)}`));
|
|
224
224
|
}
|
|
225
225
|
|
|
226
|
+
/**
|
|
227
|
+
* How each stage's rounds spent their context: turns, tool calls sent per turn, the context a turn
|
|
228
|
+
* re-read when the round began and at its largest, and how long the prompt that opened the round was. Every turn is billed the whole context again, so the
|
|
229
|
+
* cost of a round is its turns times its context, and a round that starts where a long one ended pays for
|
|
230
|
+
* all of it on every turn. The first rounds of a run are set beside the later ones, which are resumes.
|
|
231
|
+
*
|
|
232
|
+
* @param {object[]} records - The rounds in the window.
|
|
233
|
+
*/
|
|
234
|
+
function contextReport(records) {
|
|
235
|
+
const shaped = records.filter((r) => typeof r.turns === 'number' && r.turns > 0);
|
|
236
|
+
if (shaped.length === 0) return;
|
|
237
|
+
const median = (xs) => [...xs].sort((a, b) => a - b)[Math.floor(xs.length / 2)];
|
|
238
|
+
const k = (n) => (typeof n === 'number' ? `${Math.round(n / 1000)}k` : '—');
|
|
239
|
+
const chars = (xs) => (xs.length ? median(xs).toLocaleString('en-US') : '—');
|
|
240
|
+
console.log(heading('context', 'every turn re-reads the whole context: a round costs its turns times its size, and a resumed one starts where it ended; the prompt it was handed is in characters'));
|
|
241
|
+
console.log(dim(` ${'stage'.padEnd(20)}${'rounds'.padEnd(8)}${'n'.padStart(4)}${'turns'.padStart(7)}${'calls/turn'.padStart(12)}${'starts at'.padStart(11)}${'peak'.padStart(8)}${'prompt'.padStart(9)}`));
|
|
242
|
+
const byRole = new Map();
|
|
243
|
+
for (const r of shaped) byRole.set(r.role, [...(byRole.get(r.role) ?? []), r]);
|
|
244
|
+
for (const [role, rounds] of [...byRole].sort((a, b) => b[1].reduce((x, r) => x + r.turns, 0) - a[1].reduce((x, r) => x + r.turns, 0))) {
|
|
245
|
+
[
|
|
246
|
+
['first', rounds.filter((r) => (r.round ?? 1) === 1)],
|
|
247
|
+
['resumed', rounds.filter((r) => (r.round ?? 1) > 1)],
|
|
248
|
+
].forEach(([label, group], i) => {
|
|
249
|
+
if (group.length === 0) return;
|
|
250
|
+
const turns = group.reduce((a, r) => a + r.turns, 0);
|
|
251
|
+
const calls = group.reduce((a, r) => a + (r.tool_calls ?? 0), 0);
|
|
252
|
+
const name = i === 0 || !rounds.some((r) => (r.round ?? 1) === 1) ? pink(role) + ' '.repeat(Math.max(20 - role.length, 1)) : ' '.repeat(20);
|
|
253
|
+
console.log(
|
|
254
|
+
` ${name}${dim(label.padEnd(8))}${String(group.length).padStart(4)}${String(median(group.map((r) => r.turns))).padStart(7)}` +
|
|
255
|
+
`${(calls / turns).toFixed(2).padStart(12)}${k(median(group.map((r) => r.context_start ?? 0))).padStart(11)}${k(median(group.map((r) => r.context_peak ?? 0))).padStart(8)}` +
|
|
256
|
+
`${chars(group.map((r) => r.prompt_chars).filter((n) => typeof n === 'number')).padStart(9)}`,
|
|
257
|
+
);
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
226
262
|
/**
|
|
227
263
|
* Whether a model a run used is the one a spec declares: an alias (`opus`) names a family — a whole
|
|
228
264
|
* segment of the id, wherever it sits, so `claude-3-sonnet` is a sonnet — anything else names a model
|
|
@@ -722,6 +758,7 @@ export async function stats(argv, ctx) {
|
|
|
722
758
|
}
|
|
723
759
|
|
|
724
760
|
costReport(records.filter((r) => r.status !== 'denied'));
|
|
761
|
+
contextReport(records.filter((r) => r.status !== 'denied'));
|
|
725
762
|
modelReport(records.filter((r) => r.status !== 'denied'));
|
|
726
763
|
proportionReport(records.filter((r) => r.status !== 'denied'), stepLimitOf(ctx));
|
|
727
764
|
|
package/src/commands/upgrade.mjs
CHANGED
|
@@ -37,7 +37,8 @@ const BANNER = /^[\s█╗╔╝║═╚▄▀]*$|harness orchestration ·/;
|
|
|
37
37
|
* fault — everything else it finds still rolls the move back.
|
|
38
38
|
*
|
|
39
39
|
* @param {string} target - The project.
|
|
40
|
-
* @param {string[]} newSlots - Project slots this move introduces, as `<file> <slot id
|
|
40
|
+
* @param {string[]} newSlots - Project slots this move introduces, as `<file> <slot id>`, and the
|
|
41
|
+
* vocabulary names it introduces, as `{{NAME}}`.
|
|
41
42
|
* @returns {{label: string, args: string[]}[]}
|
|
42
43
|
*/
|
|
43
44
|
function validationsFor(target, newSlots = []) {
|
|
@@ -242,6 +243,10 @@ export async function upgrade(argv, ctx) {
|
|
|
242
243
|
// A slot the new release fills itself is not asked of the project: it composes as the release wrote it.
|
|
243
244
|
const defaultedAfter = await defaultedSlots(onto.dir);
|
|
244
245
|
const newSlots = [...slotsAfter].filter((s) => !slotsBefore.has(s) && !filled.has(s) && !defaultedAfter.has(s)).sort();
|
|
246
|
+
// A name only the new release uses is owed the way a new slot is: it cannot be filled with meaning
|
|
247
|
+
// before the core that asks for it is pinned, and rolling back for it left no order the move completes in.
|
|
248
|
+
const newVocab = needed.filter((v) => !vocabBefore.has(v));
|
|
249
|
+
const awaited = [...newSlots, ...newVocab.map((v) => `{{${v}}}`)];
|
|
245
250
|
const stranded = [...filled].filter((s) => !slotsAfter.has(s)).sort();
|
|
246
251
|
|
|
247
252
|
console.log(` ${profile.core} → ${to}\n`);
|
|
@@ -357,7 +362,7 @@ export async function upgrade(argv, ctx) {
|
|
|
357
362
|
// What was already failing is not this upgrade's doing, and rolling back for it would blame
|
|
358
363
|
// the move for a problem the project brought with it. Measured before anything is written,
|
|
359
364
|
// so the only rollback is for something that WAS passing and now is not.
|
|
360
|
-
const VALIDATIONS = validationsFor(target,
|
|
365
|
+
const VALIDATIONS = validationsFor(target, awaited);
|
|
361
366
|
const DETECTORS = "running the project's own detectors";
|
|
362
367
|
const hasDetectors = await declares(target, 'harness:check');
|
|
363
368
|
|
|
@@ -416,7 +421,7 @@ export async function upgrade(argv, ctx) {
|
|
|
416
421
|
// `harness:check` — an npm script whose detector list this command does not own and cannot
|
|
417
422
|
// pass an argument to. The environment is what crosses that boundary, and setting it once
|
|
418
423
|
// means a reporter added to the chain later is told without anyone remembering to tell it.
|
|
419
|
-
if (
|
|
424
|
+
if (awaited.length > 0) process.env[EXPECT_ENV] = awaited.join(',');
|
|
420
425
|
|
|
421
426
|
// Compose writes and never deletes. So the files the new version stops composing are removed right
|
|
422
427
|
// after it composes — before anything is verified, so what is verified is what stays — unless they
|
|
@@ -516,6 +521,10 @@ export async function upgrade(argv, ctx) {
|
|
|
516
521
|
console.log(`\n ${newSlots.length} project slot(s) still to fill — only this project can write them:`);
|
|
517
522
|
for (const s of newSlots) console.log(` ${s}`);
|
|
518
523
|
}
|
|
524
|
+
if (newVocab.length > 0) {
|
|
525
|
+
console.log(`\n ${newVocab.length} vocabulary name(s) ${to} asks for and this profile does not answer — fill each, or defer it with why:`);
|
|
526
|
+
console.log(` ${newVocab.join(', ')}`);
|
|
527
|
+
}
|
|
519
528
|
console.log('');
|
|
520
529
|
return 0;
|
|
521
530
|
}
|
package/src/expected.mjs
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The project slots a caller has declared it already knows are open.
|
|
2
|
+
* The project slots, and vocabulary names, a caller has declared it already knows are open.
|
|
3
3
|
*
|
|
4
4
|
* `nina upgrade --apply` creates slots: a new core can introduce one, and it cannot be filled
|
|
5
|
-
* before the core that introduces it is pinned.
|
|
5
|
+
* before the core that introduces it is pinned. A name only the new core uses is the same case,
|
|
6
|
+
* named as `{{NAME}}`: `check` rolled a move back for one the preview had just listed as needed. Every reporter that fails on an unfilled slot
|
|
6
7
|
* therefore has to be told, or the move deadlocks — there is no order in which it completes.
|
|
7
8
|
*
|
|
8
9
|
* Two reporters say this same thing, and the first fix taught only one: `check` counts a slot
|
|
@@ -21,7 +22,7 @@ export const EXPECT_ENV = 'NINA_EXPECT_UNFILLED';
|
|
|
21
22
|
/**
|
|
22
23
|
* @param {string[]} argv - The command's arguments; `--expect-unfilled <a,b>` names slots.
|
|
23
24
|
* @param {NodeJS.ProcessEnv} [env] - Where to read `NINA_EXPECT_UNFILLED` from.
|
|
24
|
-
* @returns {Set<string>} Slot ids as `<relative path> <slot id
|
|
25
|
+
* @returns {Set<string>} Slot ids as `<relative path> <slot id>`, names as `{{NAME}}`.
|
|
25
26
|
*/
|
|
26
27
|
export function expectedUnfilled(argv = [], env = process.env) {
|
|
27
28
|
const fromArgv = argv.includes('--expect-unfilled') ? argv[argv.indexOf('--expect-unfilled') + 1] ?? '' : '';
|
package/src/langfuse.mjs
CHANGED
|
@@ -100,31 +100,47 @@ function usageAttributes(tokens, model, priced) {
|
|
|
100
100
|
return out;
|
|
101
101
|
}
|
|
102
102
|
|
|
103
|
+
/**
|
|
104
|
+
* How much of a stage's context a project sends, from its switch in the Langfuse configuration: all of it
|
|
105
|
+
* (`--content`), only what passed between the agents (`--prompts`), or none.
|
|
106
|
+
*
|
|
107
|
+
* @param {{content?: boolean|string}|undefined} setting - The project's entry, if it is on.
|
|
108
|
+
* @returns {'full'|'prompts'|null}
|
|
109
|
+
*/
|
|
110
|
+
export function contextMode(setting) {
|
|
111
|
+
return setting?.content === 'prompts' ? 'prompts' : setting?.content ? 'full' : null;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/** A round's shape, as metadata on its root: how many turns and calls it took, and how large its context and prompt were. */
|
|
115
|
+
const SHAPE = ['turns', 'tool_calls', 'context_start', 'context_peak', 'prompt_chars'];
|
|
116
|
+
|
|
103
117
|
/**
|
|
104
118
|
* The OTLP span at the root of a dispatch's trace. Without its context, a run that spent tokens is a
|
|
105
119
|
* generation carrying the run's whole usage and estimated cost, and one that did not is a span. With it,
|
|
106
120
|
* the root is the agent — its prompt in, its report out — and the usage is on the messages beneath it,
|
|
107
|
-
* so no sum counts a token twice.
|
|
108
|
-
*
|
|
109
|
-
*
|
|
121
|
+
* so no sum counts a token twice. With the prompt and report alone, nothing is beneath it, so the root is
|
|
122
|
+
* a generation again, carrying the usage beside them. Its verdict is the score beside it rather than an
|
|
123
|
+
* attribute on it: a run whose verdict is read only later can still be given one, since that is a first
|
|
124
|
+
* score and not a second span.
|
|
110
125
|
*
|
|
111
126
|
* @param {object} record - A snapshot record.
|
|
112
127
|
* @param {string} project - The name the project goes by in Langfuse.
|
|
113
128
|
* @param {{prompt: string, report: string}} [context] - What the stage was given and handed back.
|
|
129
|
+
* @param {{usage?: boolean}} [options] - `usage`: the root carries the usage even with its context.
|
|
114
130
|
* @returns {object|null} Null for a record with no time to place it at.
|
|
115
131
|
*/
|
|
116
|
-
export function spanOf(record, project, context = null) {
|
|
132
|
+
export function spanOf(record, project, context = null, { usage = false } = {}) {
|
|
117
133
|
const start = nanos(record.ts);
|
|
118
134
|
if (!start) return null;
|
|
119
135
|
const end = nanos(record.result_ts) ?? start;
|
|
120
|
-
const tokens = !context && record.tokens && typeof record.tokens === 'object' ? record.tokens : null;
|
|
136
|
+
const tokens = (!context || usage) && record.tokens && typeof record.tokens === 'object' ? record.tokens : null;
|
|
121
137
|
const attributes = [
|
|
122
138
|
attribute('langfuse.session.id', record.session ?? 'no-session'),
|
|
123
139
|
attribute('langfuse.trace.name', record.role),
|
|
124
140
|
attribute('langfuse.trace.tags', ['nina', project]),
|
|
125
141
|
attribute('langfuse.trace.metadata.project', project),
|
|
126
142
|
attribute('langfuse.trace.metadata.role', record.role),
|
|
127
|
-
attribute('langfuse.observation.type',
|
|
143
|
+
attribute('langfuse.observation.type', tokens ? 'generation' : context ? 'agent' : 'span'),
|
|
128
144
|
attribute('langfuse.observation.metadata.role', record.role),
|
|
129
145
|
];
|
|
130
146
|
if (typeof record.issues === 'number') attributes.push(attribute('langfuse.observation.metadata.issues', record.issues));
|
|
@@ -133,6 +149,7 @@ export function spanOf(record, project, context = null) {
|
|
|
133
149
|
if (record.effort) attributes.push(attribute('langfuse.observation.metadata.effort', record.effort));
|
|
134
150
|
// A round after the first is the same agent resumed after it reported; the trace says which.
|
|
135
151
|
if (typeof record.round === 'number') attributes.push(attribute('langfuse.observation.metadata.round', record.round));
|
|
152
|
+
for (const key of SHAPE) if (typeof record[key] === 'number') attributes.push(attribute(`langfuse.observation.metadata.${key}`, record[key]));
|
|
136
153
|
if (tokens) attributes.push(...usageAttributes(tokens, record.usage_model, record.usage_model));
|
|
137
154
|
if (context) {
|
|
138
155
|
attributes.push(attribute('langfuse.observation.input', context.prompt ?? ''));
|
|
@@ -144,23 +161,26 @@ export function spanOf(record, project, context = null) {
|
|
|
144
161
|
/**
|
|
145
162
|
* Everything a dispatch sends: the root, and — when its context goes too — a generation per message it
|
|
146
163
|
* wrote, with that message's own tokens and cost, and an observation per tool it called, with what went
|
|
147
|
-
* in and what came back.
|
|
148
|
-
*
|
|
164
|
+
* in and what came back. With `prompts`, only what passed between the agents goes: the root, with the
|
|
165
|
+
* prompt the stage was given and the report it handed back, and none of what it read, wrote or called in
|
|
166
|
+
* between. Every text is masked (`redact`), cut to a length, and has the owner's home directory written
|
|
167
|
+
* as `~`.
|
|
149
168
|
*
|
|
150
169
|
* @param {object} record - A snapshot record.
|
|
151
170
|
* @param {string} project - The name the project goes by in Langfuse.
|
|
152
171
|
* @param {object|null} run - From `readRun`, or null to send the record alone.
|
|
153
172
|
* @param {{text?: number, tool?: number}} [limits] - How much of each text goes.
|
|
173
|
+
* @param {{prompts?: boolean}} [options] - `prompts`: the prompt and the report, and nothing beneath them.
|
|
154
174
|
* @returns {object[]} Spans; empty for a record with no time to place it at.
|
|
155
175
|
*/
|
|
156
|
-
export function spansOf(record, project, run, limits = {}) {
|
|
176
|
+
export function spansOf(record, project, run, limits = {}, { prompts = false } = {}) {
|
|
157
177
|
const textChars = limits.text ?? TEXT_CHARS;
|
|
158
178
|
const toolChars = limits.tool ?? TOOL_CHARS;
|
|
159
179
|
const home = homedir();
|
|
160
180
|
const clean = (text, chars) => clip(redact(String(text ?? '')).replaceAll(home, '~'), chars);
|
|
161
|
-
const root = spanOf(record, project, run ? { prompt: clean(run.prompt, textChars), report: clean(run.report, textChars) } : null);
|
|
181
|
+
const root = spanOf(record, project, run ? { prompt: clean(run.prompt, textChars), report: clean(run.report, textChars) } : null, { usage: prompts });
|
|
162
182
|
if (!root) return [];
|
|
163
|
-
if (!run) return [root];
|
|
183
|
+
if (!run || prompts) return [root];
|
|
164
184
|
const child = (id, name, start, end, attributes) => ({
|
|
165
185
|
traceId: root.traceId,
|
|
166
186
|
spanId: hexId(`${record.dispatch_id}|${id}`, 16),
|
package/src/transcripts.mjs
CHANGED
|
@@ -626,9 +626,13 @@ async function attachAgentDetail(projectDir, dispatches) {
|
|
|
626
626
|
// 744 of one project's 826 in full on every turn, two seconds before the person saw an answer.
|
|
627
627
|
const size = (await stat(join(dir, file)).catch(() => null))?.size ?? null;
|
|
628
628
|
const settled = (r) => r.handed_back === false || String(r.verdict_source ?? '').startsWith('handback');
|
|
629
|
+
// `resumes` is written only by a reader from before rounds, which bills the whole run to its first
|
|
630
|
+
// record. A store both versions wrote to — one project on the old pin, a hook elsewhere on the new —
|
|
631
|
+
// had its runs marked read by the old one and skipped here, their first round billed twice.
|
|
629
632
|
if (
|
|
630
633
|
run.agent_read_bytes === size &&
|
|
631
|
-
|
|
634
|
+
!('resumes' in run) &&
|
|
635
|
+
[...records, ...later].every((r) => settled(r) && 'round' in r && 'lessons_read' in r && 'tokens' in r && 'files_touched' in r && 'effort' in r && 'turns' in r && 'prompt_chars' in r)
|
|
632
636
|
) {
|
|
633
637
|
continue;
|
|
634
638
|
}
|
|
@@ -656,6 +660,10 @@ async function attachAgentDetail(projectDir, dispatches) {
|
|
|
656
660
|
usage: new Map(),
|
|
657
661
|
/** The files the round wrote through the edit tools. Only their number is kept. */
|
|
658
662
|
written: new Set(),
|
|
663
|
+
/** The tool calls the round made, by id: over its turns, how many calls each turn sent at once. */
|
|
664
|
+
calls: new Set(),
|
|
665
|
+
/** How long the prompt that opened the round was, in characters; never the prompt. */
|
|
666
|
+
promptChars: null,
|
|
659
667
|
});
|
|
660
668
|
const rounds = [fresh()];
|
|
661
669
|
const roundOf = roundsOf();
|
|
@@ -675,8 +683,17 @@ async function attachAgentDetail(projectDir, dispatches) {
|
|
|
675
683
|
}
|
|
676
684
|
}
|
|
677
685
|
const round = rounds[n - 1];
|
|
686
|
+
if (round.promptChars === null && line.includes('"type":"user"')) {
|
|
687
|
+
try {
|
|
688
|
+
const said = promptOf(JSON.parse(line), n);
|
|
689
|
+
if (said !== null) round.promptChars = said.length;
|
|
690
|
+
} catch {
|
|
691
|
+
// A torn line is not the prompt.
|
|
692
|
+
}
|
|
693
|
+
}
|
|
678
694
|
const hasSkill = line.includes('"Skill"');
|
|
679
695
|
const hasHandback = line.includes('SubagentHandback');
|
|
696
|
+
for (const m of line.matchAll(/"type":"tool_use","id":"([^"]+)"/g)) round.calls.add(m[1]);
|
|
680
697
|
if (/"name":"(Edit|Write|MultiEdit|NotebookEdit)"/.test(line)) {
|
|
681
698
|
try {
|
|
682
699
|
for (const block of JSON.parse(line)?.message?.content ?? []) {
|
|
@@ -755,9 +772,15 @@ async function attachAgentDetail(projectDir, dispatches) {
|
|
|
755
772
|
targets = [dispatches.get(id)];
|
|
756
773
|
}
|
|
757
774
|
const spent = tokensOf(round.usage);
|
|
775
|
+
const shape = contextOf(round.usage);
|
|
758
776
|
targets.forEach((record, j) => {
|
|
759
777
|
// One transcript, one bill: were two records ever to share an agent, the second would count it again.
|
|
760
778
|
record.tokens = j === 0 ? spent.tokens : null;
|
|
779
|
+
record.turns = shape.turns;
|
|
780
|
+
record.tool_calls = round.calls.size;
|
|
781
|
+
record.context_start = shape.start;
|
|
782
|
+
record.context_peak = shape.peak;
|
|
783
|
+
record.prompt_chars = round.promptChars;
|
|
761
784
|
record.usage_model = spent.model;
|
|
762
785
|
record.effort = spent.effort;
|
|
763
786
|
record.files_touched = round.written.size;
|
|
@@ -790,6 +813,35 @@ async function attachAgentDetail(projectDir, dispatches) {
|
|
|
790
813
|
}
|
|
791
814
|
}
|
|
792
815
|
|
|
816
|
+
/**
|
|
817
|
+
* What a round was told, from one transcript row, or null when the row is not it. The dispatch's prompt
|
|
818
|
+
* opens the first round; a later round opens with the message that resumed the run, which Claude Code
|
|
819
|
+
* marks as meta, while a meta row in the first round is a reminder. The snapshot measures it and the
|
|
820
|
+
* Langfuse export sends it, so both read it here.
|
|
821
|
+
*
|
|
822
|
+
* @param {object} row - A parsed transcript row.
|
|
823
|
+
* @param {number} round - The round the row is in, from 1.
|
|
824
|
+
* @returns {string|null}
|
|
825
|
+
*/
|
|
826
|
+
export function promptOf(row, round) {
|
|
827
|
+
const content = row?.message?.content;
|
|
828
|
+
return row?.type === 'user' && (round > 1 || !row.isMeta) && typeof content === 'string' ? content : null;
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
/**
|
|
832
|
+
* How a round spent its context: how many turns it took, and how large the context each turn re-read was
|
|
833
|
+
* when the round began and at its largest. Every turn is billed the whole context again, so a round that
|
|
834
|
+
* begins where a long one ended — a run resumed for a fix — pays for everything before it on each turn.
|
|
835
|
+
*
|
|
836
|
+
* @param {Map<string, {usage: object}>} usage - Message id → its final usage, in the order the round sent them.
|
|
837
|
+
* @returns {{turns: number, start: number|null, peak: number|null}} Context sizes in tokens.
|
|
838
|
+
*/
|
|
839
|
+
export function contextOf(usage) {
|
|
840
|
+
const n = (v) => (typeof v === 'number' ? v : 0);
|
|
841
|
+
const sizes = [...usage.values()].map(({ usage: u }) => n(u.input_tokens) + n(u.cache_read_input_tokens) + n(u.cache_creation_input_tokens));
|
|
842
|
+
return { turns: sizes.length, start: sizes[0] ?? null, peak: sizes.length > 0 ? Math.max(...sizes) : null };
|
|
843
|
+
}
|
|
844
|
+
|
|
793
845
|
/**
|
|
794
846
|
* What a run spent, from each of its messages' final usage: tokens by kind, and the model and effort
|
|
795
847
|
* level that most of its messages ran at. Metadata — counts, a model id and a level, never a message.
|