@tangle-network/agent-eval 0.123.8 → 0.125.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/CHANGELOG.md +29 -4
- package/dist/analyst/index.d.ts +5 -0
- package/dist/analyst/index.js +4 -4
- package/dist/benchmarks/index.js +4 -4
- package/dist/campaign/index.d.ts +20 -1
- package/dist/campaign/index.js +4 -4
- package/dist/{chunk-ZU3QWGZE.js → chunk-A62YMFWA.js} +89 -8
- package/dist/chunk-A62YMFWA.js.map +1 -0
- package/dist/{chunk-BUR5R4R4.js → chunk-A6GT67HT.js} +3 -3
- package/dist/{chunk-EEHHBAWH.js → chunk-DT7OXY3C.js} +2 -2
- package/dist/{chunk-PDHIOKRE.js → chunk-EQUK3RFS.js} +8 -4
- package/dist/chunk-EQUK3RFS.js.map +1 -0
- package/dist/{chunk-V7HQGZBT.js → chunk-GID26AN4.js} +2 -2
- package/dist/{chunk-27UXRPIQ.js → chunk-HM6V7F3M.js} +2 -2
- package/dist/chunk-IPYXE555.js +594 -0
- package/dist/chunk-IPYXE555.js.map +1 -0
- package/dist/chunk-LKKT3IVV.js +1644 -0
- package/dist/chunk-LKKT3IVV.js.map +1 -0
- package/dist/chunk-M7AH34KV.js +155 -0
- package/dist/chunk-M7AH34KV.js.map +1 -0
- package/dist/chunk-MAX3TN3C.js +249 -0
- package/dist/chunk-MAX3TN3C.js.map +1 -0
- package/dist/{chunk-QVGVJQMR.js → chunk-PMITBABE.js} +7 -5
- package/dist/{chunk-QVGVJQMR.js.map → chunk-PMITBABE.js.map} +1 -1
- package/dist/{chunk-J3LHTAAB.js → chunk-QOTFXW5L.js} +1 -47
- package/dist/chunk-QOTFXW5L.js.map +1 -0
- package/dist/chunk-RZTMDUO7.js +49 -0
- package/dist/chunk-RZTMDUO7.js.map +1 -0
- package/dist/chunk-VBQ3CRKH.js +291 -0
- package/dist/chunk-VBQ3CRKH.js.map +1 -0
- package/dist/{chunk-RQ5TP2TV.js → chunk-W5B3ZGP3.js} +3 -3
- package/dist/cli.js +13 -2
- package/dist/cli.js.map +1 -1
- package/dist/contract/index.d.ts +7 -0
- package/dist/contract/index.js +4 -4
- package/dist/index.d.ts +833 -82
- package/dist/index.js +61 -121
- package/dist/index.js.map +1 -1
- package/dist/openapi.json +1 -1
- package/dist/pipelines/index.js +4 -2
- package/dist/pipelines/index.js.map +1 -1
- package/dist/rl.d.ts +3 -0
- package/dist/rl.js +2 -2
- package/dist/rollout/index.d.ts +1061 -0
- package/dist/rollout/index.js +109 -0
- package/dist/rollout/index.js.map +1 -0
- package/dist/supervisor-run/index.d.ts +857 -0
- package/dist/supervisor-run/index.js +64 -0
- package/dist/supervisor-run/index.js.map +1 -0
- package/dist/wire/index.d.ts +3 -0
- package/dist/wire/index.js +2 -2
- package/docs/rollout.md +48 -0
- package/package.json +11 -1
- package/dist/chunk-J3LHTAAB.js.map +0 -1
- package/dist/chunk-PDHIOKRE.js.map +0 -1
- package/dist/chunk-ZU3QWGZE.js.map +0 -1
- /package/dist/{chunk-BUR5R4R4.js.map → chunk-A6GT67HT.js.map} +0 -0
- /package/dist/{chunk-EEHHBAWH.js.map → chunk-DT7OXY3C.js.map} +0 -0
- /package/dist/{chunk-V7HQGZBT.js.map → chunk-GID26AN4.js.map} +0 -0
- /package/dist/{chunk-27UXRPIQ.js.map → chunk-HM6V7F3M.js.map} +0 -0
- /package/dist/{chunk-RQ5TP2TV.js.map → chunk-W5B3ZGP3.js.map} +0 -0
|
@@ -8,51 +8,6 @@ import {
|
|
|
8
8
|
toolSpans
|
|
9
9
|
} from "./chunk-ZET2UAYW.js";
|
|
10
10
|
|
|
11
|
-
// src/trajectory.ts
|
|
12
|
-
async function buildTrajectory(store, runId) {
|
|
13
|
-
const spans = await store.spans({ runId });
|
|
14
|
-
const events = await store.events({ runId });
|
|
15
|
-
const childrenOf = /* @__PURE__ */ new Map();
|
|
16
|
-
for (const s of spans) {
|
|
17
|
-
const arr = childrenOf.get(s.parentSpanId) ?? [];
|
|
18
|
-
arr.push(s);
|
|
19
|
-
childrenOf.set(s.parentSpanId, arr);
|
|
20
|
-
}
|
|
21
|
-
for (const arr of childrenOf.values()) arr.sort((a, b) => a.startedAt - b.startedAt);
|
|
22
|
-
const eventsBySpan = /* @__PURE__ */ new Map();
|
|
23
|
-
for (const e of events) {
|
|
24
|
-
if (!e.spanId) continue;
|
|
25
|
-
const arr = eventsBySpan.get(e.spanId) ?? [];
|
|
26
|
-
arr.push(e);
|
|
27
|
-
eventsBySpan.set(e.spanId, arr);
|
|
28
|
-
}
|
|
29
|
-
const steps = [];
|
|
30
|
-
const walk = (spanId, depth) => {
|
|
31
|
-
const kids = childrenOf.get(spanId) ?? [];
|
|
32
|
-
for (const child of kids) {
|
|
33
|
-
steps.push({
|
|
34
|
-
index: steps.length,
|
|
35
|
-
span: child,
|
|
36
|
-
depth,
|
|
37
|
-
events: eventsBySpan.get(child.spanId) ?? []
|
|
38
|
-
});
|
|
39
|
-
walk(child.spanId, depth + 1);
|
|
40
|
-
}
|
|
41
|
-
};
|
|
42
|
-
walk(void 0, 0);
|
|
43
|
-
const llmTurns = steps.filter((s) => s.span.kind === "llm").length;
|
|
44
|
-
const toolCalls = steps.filter((s) => s.span.kind === "tool").length;
|
|
45
|
-
const judgeVerdicts = steps.filter((s) => s.span.kind === "judge").length;
|
|
46
|
-
const retrievals = steps.filter((s) => s.span.kind === "retrieval").length;
|
|
47
|
-
let totalDurationMs = 0;
|
|
48
|
-
if (steps.length > 0) {
|
|
49
|
-
const starts = spans.map((s) => s.startedAt);
|
|
50
|
-
const ends = spans.map((s) => s.endedAt ?? s.startedAt);
|
|
51
|
-
totalDurationMs = Math.max(...ends) - Math.min(...starts);
|
|
52
|
-
}
|
|
53
|
-
return { runId, steps, llmTurns, toolCalls, judgeVerdicts, retrievals, totalDurationMs };
|
|
54
|
-
}
|
|
55
|
-
|
|
56
11
|
// src/failure-taxonomy.ts
|
|
57
12
|
var DEFAULT_RULES = [
|
|
58
13
|
// Outcome already named? Respect it.
|
|
@@ -611,7 +566,6 @@ function normalCdf(x) {
|
|
|
611
566
|
}
|
|
612
567
|
|
|
613
568
|
export {
|
|
614
|
-
buildTrajectory,
|
|
615
569
|
DEFAULT_RULES,
|
|
616
570
|
classifyFailure,
|
|
617
571
|
computeToolUseMetrics,
|
|
@@ -619,4 +573,4 @@ export {
|
|
|
619
573
|
iqr,
|
|
620
574
|
welchsTTest
|
|
621
575
|
};
|
|
622
|
-
//# sourceMappingURL=chunk-
|
|
576
|
+
//# sourceMappingURL=chunk-QOTFXW5L.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/failure-taxonomy.ts","../src/tool-use-metrics.ts","../src/baseline.ts"],"sourcesContent":["/**\n * Failure taxonomy — canonical classes + a default classifier.\n *\n * Every failed run should end up in a named class. The classifier here\n * is rule-based (fast, deterministic); an LLM fallback can be added by\n * the consumer for novel cases and trained into the rule base over time.\n *\n * Consumers call `classifyFailure(run, spans, events)` and persist the\n * returned class as `Run.outcome.failureClass`.\n */\n\nimport type { FailureClass, Run, Span, TraceEvent } from './trace/schema'\nimport { FAILURE_CLASSES } from './trace/schema'\n\nexport { FAILURE_CLASSES, type FailureClass }\n\nexport interface FailureContext {\n run: Run\n spans: Span[]\n events: TraceEvent[]\n}\n\nexport interface FailureClassification {\n failureClass: FailureClass\n reason: string\n triggerSpanId?: string\n triggerEventId?: string\n}\n\n/** Ordered rules — first match wins. */\nexport interface FailureRule {\n id: string\n match: (ctx: FailureContext) => {\n failureClass: FailureClass\n reason: string\n triggerSpanId?: string\n triggerEventId?: string\n } | null\n}\n\nexport const DEFAULT_RULES: FailureRule[] = [\n // Outcome already named? Respect it.\n {\n id: 'explicit-outcome',\n match: ({ run }) => {\n const fc = run.outcome?.failureClass\n if (fc && fc !== 'unknown')\n return { failureClass: fc, reason: 'outcome.failureClass set explicitly' }\n return null\n },\n },\n {\n id: 'knowledge-readiness-blocked',\n match: ({ events }) => {\n const event = events.find(\n (e) =>\n e.kind === 'custom' &&\n e.payload.kind === 'readiness_scored' &&\n e.payload.passed === false,\n )\n return event\n ? {\n failureClass: 'knowledge_readiness_blocked',\n reason: 'knowledge readiness report blocked execution',\n triggerEventId: event.eventId,\n }\n : null\n },\n },\n {\n id: 'bad-integration-manifest',\n match: ({ events }) => {\n const event = events.find(\n (e) =>\n e.kind === 'custom' &&\n ((e.payload.kind === 'integration_manifest_validated' && e.payload.valid === false) ||\n (e.payload.kind === 'integration_invoke_failed' &&\n e.payload.code === 'manifest_invalid')),\n )\n return event\n ? {\n failureClass: 'bad_integration_manifest',\n reason: 'integration manifest validation failed before launch',\n triggerEventId: event.eventId,\n }\n : null\n },\n },\n {\n id: 'missing-integration-connection',\n match: ({ events }) => {\n const event = events.find(\n (e) =>\n e.kind === 'custom' &&\n e.payload.kind === 'integration_manifest_resolved' &&\n hasResolutionStatus(e.payload, 'missing_connection'),\n )\n return event\n ? {\n failureClass: 'missing_integration_connection',\n reason: 'required integration connection was missing',\n triggerEventId: event.eventId,\n }\n : null\n },\n },\n {\n id: 'missing-integration-scope',\n match: ({ events }) => {\n const event = events.find(\n (e) =>\n e.kind === 'custom' &&\n ((e.payload.kind === 'integration_manifest_resolved' && hasMissingScopes(e.payload)) ||\n (e.payload.kind === 'integration_invoke_failed' && e.payload.code === 'scope_denied')),\n )\n return event\n ? {\n failureClass: 'missing_integration_scope',\n reason: 'integration grant or connection lacks required scopes',\n triggerEventId: event.eventId,\n }\n : null\n },\n },\n {\n id: 'integration-approval-required',\n match: ({ events }) => {\n const event = events.find(\n (e) =>\n e.kind === 'custom' &&\n ((e.payload.kind === 'integration_invoke' && e.payload.status === 'approval_required') ||\n (e.payload.kind === 'integration_invoke_failed' &&\n e.payload.code === 'approval_required') ||\n e.payload.kind === 'integration_approval_required'),\n )\n return event\n ? {\n failureClass: 'integration_approval_required',\n reason: 'integration write paused for user approval',\n triggerEventId: event.eventId,\n }\n : null\n },\n },\n {\n id: 'integration-auth-expired',\n match: ({ events }) => {\n const event = events.find(\n (e) =>\n e.kind === 'custom' &&\n e.payload.kind === 'integration_invoke_failed' &&\n (e.payload.code === 'auth_expired' ||\n e.payload.code === 'connection_not_active' ||\n e.payload.code === 'capability_expired' ||\n e.payload.status === 'expired'),\n )\n return event\n ? {\n failureClass: 'integration_auth_expired',\n reason: 'integration connection or capability expired',\n triggerEventId: event.eventId,\n }\n : null\n },\n },\n {\n id: 'unsafe-integration-write-denied',\n match: ({ events }) => {\n const event = events.find(\n (e) =>\n e.kind === 'custom' &&\n e.payload.kind === 'integration_invoke_failed' &&\n (e.payload.code === 'unsafe_write_denied' ||\n e.payload.code === 'policy_denied' ||\n e.payload.code === 'action_denied'),\n )\n return event\n ? {\n failureClass: 'unsafe_integration_write_denied',\n reason: 'integration write was denied by policy or capability scope',\n triggerEventId: event.eventId,\n }\n : null\n },\n },\n {\n id: 'integration-provider-failure',\n match: ({ events }) => {\n const event = events.find(\n (e) =>\n e.kind === 'custom' &&\n e.payload.kind === 'integration_invoke_failed' &&\n ![\n 'scope_denied',\n 'approval_required',\n 'auth_expired',\n 'connection_not_active',\n 'capability_expired',\n 'unsafe_write_denied',\n 'policy_denied',\n 'action_denied',\n 'manifest_invalid',\n ].includes(String(e.payload.code)),\n )\n return event\n ? {\n failureClass: 'integration_provider_failure',\n reason: 'integration provider invocation failed',\n triggerEventId: event.eventId,\n }\n : null\n },\n },\n {\n id: 'missing-credentials',\n match: ({ events }) => {\n const event = events.find(\n (e) =>\n e.kind === 'custom' &&\n e.payload.kind === 'knowledge_gap' &&\n e.payload.category === 'credential_or_secret',\n )\n return event\n ? {\n failureClass: 'missing_credentials',\n reason: 'required credential or secret was missing',\n triggerEventId: event.eventId,\n }\n : null\n },\n },\n {\n id: 'bad-retrieval',\n match: ({ run, spans }) => {\n if (run.outcome?.pass !== false) return null\n const retrieval = spans.find(\n (s) =>\n s.kind === 'retrieval' && (s.hits.length === 0 || s.hits.every((hit) => hit.score <= 0)),\n )\n return retrieval\n ? {\n failureClass: 'bad_retrieval',\n reason: 'retrieval returned no useful hits for a failed run',\n triggerSpanId: retrieval.spanId,\n }\n : null\n },\n },\n {\n id: 'insufficient-evidence',\n match: ({ events }) => {\n const event = events.find(\n (e) =>\n e.kind === 'custom' &&\n e.payload.kind === 'knowledge_gap' &&\n e.payload.reason === 'insufficient_evidence',\n )\n return event\n ? {\n failureClass: 'insufficient_evidence',\n reason: 'task proceeded with insufficient supporting evidence',\n triggerEventId: event.eventId,\n }\n : null\n },\n },\n {\n id: 'contradictory-evidence',\n match: ({ events }) => {\n const event = events.find(\n (e) =>\n e.kind === 'custom' &&\n e.payload.kind === 'knowledge_gap' &&\n e.payload.reason === 'contradictory_evidence',\n )\n return event\n ? {\n failureClass: 'contradictory_evidence',\n reason: 'supporting evidence contradicted itself',\n triggerEventId: event.eventId,\n }\n : null\n },\n },\n // Budget breach events\n {\n id: 'budget-breach',\n match: ({ events }) => {\n const breach = events.find((e) => e.kind === 'budget_breach')\n return breach\n ? {\n failureClass: 'budget_exceeded',\n reason: `budget breached on ${breach.payload.dimension ?? 'unknown dimension'}`,\n triggerEventId: breach.eventId,\n }\n : null\n },\n },\n // Policy violations\n {\n id: 'policy-violation',\n match: ({ events }) => {\n const e = events.find((x) => x.kind === 'policy_violation')\n return e\n ? {\n failureClass: 'policy_violation',\n reason: 'policy_violation event emitted',\n triggerEventId: e.eventId,\n }\n : null\n },\n },\n // Sandbox non-zero exit code\n {\n id: 'sandbox-failure',\n match: ({ spans }) => {\n const s = spans.find(\n (x) => x.kind === 'sandbox' && typeof x.exitCode === 'number' && x.exitCode !== 0,\n )\n if (!s) return null\n return {\n failureClass: 'sandbox_failure',\n reason: `sandbox exited ${(s as Extract<Span, { kind: 'sandbox' }>).exitCode}`,\n triggerSpanId: s.spanId,\n }\n },\n },\n // Timeout: run aborted by external signal\n {\n id: 'timeout',\n match: ({ run, events }) => {\n if (run.status !== 'aborted') return null\n const hasTimeout = events.some(\n (e) =>\n e.kind === 'error' &&\n String(e.payload.reason ?? '')\n .toLowerCase()\n .includes('timeout'),\n )\n const note = (run.outcome?.notes ?? '').toLowerCase()\n if (hasTimeout || note.includes('timeout') || note.includes('deadline')) {\n return { failureClass: 'timeout', reason: 'timeout signal observed' }\n }\n return null\n },\n },\n // Tool recovery failure: many consecutive tool errors on the same tool\n {\n id: 'tool-recovery-failure',\n match: ({ spans }) => {\n const tools = spans.filter((s) => s.kind === 'tool')\n const byTool = new Map<string, Span[]>()\n for (const t of tools) {\n const name = (t as Extract<Span, { kind: 'tool' }>).toolName\n const arr = byTool.get(name) ?? []\n arr.push(t)\n byTool.set(name, arr)\n }\n for (const [name, arr] of byTool) {\n const errs = arr.filter((s) => s.status === 'error')\n if (errs.length >= 3 && errs.length === arr.length) {\n return {\n failureClass: 'tool_recovery_failure',\n reason: `${errs.length} consecutive errors on tool \"${name}\"`,\n triggerSpanId: errs[errs.length - 1]!.spanId,\n }\n }\n }\n return null\n },\n },\n // Tool selection error: the run failed and agent called zero tools despite having them\n {\n id: 'tool-selection-error',\n match: ({ run, spans }) => {\n if (run.outcome?.pass !== false) return null\n const hasToolsAvailable = spans.some(\n (s) =>\n s.kind === 'agent' &&\n (s.attributes?.toolsAvailable as number | undefined) !== undefined &&\n (s.attributes?.toolsAvailable as number) > 0,\n )\n const tools = spans.filter((s) => s.kind === 'tool')\n if (hasToolsAvailable && tools.length === 0) {\n return {\n failureClass: 'tool_selection_error',\n reason: 'tools were available but none were called',\n }\n }\n return null\n },\n },\n // Format drift: scored by a judge with dimension='format' below threshold\n {\n id: 'format-drift',\n match: ({ spans }) => {\n const judge = spans.find(\n (s) =>\n s.kind === 'judge' &&\n (s as Extract<Span, { kind: 'judge' }>).dimension === 'format' &&\n (s as Extract<Span, { kind: 'judge' }>).score < 0.5,\n )\n return judge\n ? {\n failureClass: 'format_drift',\n reason: 'format judge scored below 0.5',\n triggerSpanId: judge.spanId,\n }\n : null\n },\n },\n]\n\nfunction hasResolutionStatus(payload: Record<string, unknown>, status: string): boolean {\n if (status === 'missing_connection' && stringArray(payload.missingConnections).length > 0)\n return true\n return resolutionItems(payload).some((item) => item.status === status)\n}\n\nfunction hasMissingScopes(payload: Record<string, unknown>): boolean {\n if (stringArray(payload.missingScopes).length > 0) return true\n return resolutionItems(payload).some(\n (item) => Array.isArray(item.missingScopes) && item.missingScopes.length > 0,\n )\n}\n\nfunction resolutionItems(payload: Record<string, unknown>): Array<Record<string, unknown>> {\n return [\n ...records(payload.missing),\n ...records(payload.optionalMissing),\n ...records(payload.ready),\n ]\n}\n\nfunction records(value: unknown): Array<Record<string, unknown>> {\n if (!Array.isArray(value)) return []\n return value.filter(\n (item): item is Record<string, unknown> =>\n Boolean(item) && typeof item === 'object' && !Array.isArray(item),\n )\n}\n\nfunction stringArray(value: unknown): string[] {\n return Array.isArray(value)\n ? value.filter((item): item is string => typeof item === 'string')\n : []\n}\n\n/** Classify the failure mode of a run using an ordered rule list. */\nexport function classifyFailure(\n ctx: FailureContext,\n rules: FailureRule[] = DEFAULT_RULES,\n): FailureClassification {\n if (ctx.run.outcome?.pass !== false && ctx.run.status === 'completed') {\n return { failureClass: 'success', reason: 'run completed with pass=true (or no explicit fail)' }\n }\n for (const rule of rules) {\n const hit = rule.match(ctx)\n if (hit) return hit\n }\n return { failureClass: 'unknown', reason: 'no rule matched; run failed for unclassified reason' }\n}\n","/**\n * Tool-use metrics — derived purely from trace data.\n *\n * No scoring assumptions: consumers supply optional ground-truth tool\n * selections per turn + optional \"information used downstream\" signals.\n * Without those, we still compute descriptive metrics (error rate,\n * retry rate, duplicate-call rate) that are useful on their own.\n */\n\nimport { argHash, groupBy, hasCapturedToolArgs, toolSpans } from './trace/query'\nimport type { Span } from './trace/schema'\nimport type { TraceStore } from './trace/store'\n\nexport interface ToolUseMetrics {\n runId: string\n totalCalls: number\n /** Calls whose arguments were captured and can be compared for duplication. */\n callsWithCapturedArgs: number\n byTool: Record<string, ToolStats>\n errorRate: number\n /** Ratio of captured-argument calls already seen with the same tool name and arguments. */\n duplicateRate: number\n /** Ratio of error calls followed by ≥1 retry on same tool. */\n retryRate: number\n /** Optional: of the calls agent made, fraction the evaluator marked as \"correct selection\". */\n selectionAccuracy?: number\n}\n\nexport interface ToolStats {\n calls: number\n callsWithCapturedArgs: number\n errors: number\n avgLatencyMs: number\n duplicates: number\n}\n\nexport interface ToolUseOptions {\n /** Map of spanId → whether the evaluator judged the tool selection correct. Optional. */\n selectionLabels?: Record<string, boolean>\n}\n\nexport async function computeToolUseMetrics(\n store: TraceStore,\n runId: string,\n options: ToolUseOptions = {},\n): Promise<ToolUseMetrics> {\n const tools = await toolSpans(store, runId)\n if (tools.length === 0) {\n return {\n runId,\n totalCalls: 0,\n callsWithCapturedArgs: 0,\n byTool: {},\n errorRate: 0,\n duplicateRate: 0,\n retryRate: 0,\n }\n }\n\n const byTool: Record<string, ToolStats> = {}\n let totalErrors = 0\n let totalDuplicates = 0\n let callsWithCapturedArgs = 0\n const sortedTools = [...tools].sort((a, b) => a.startedAt - b.startedAt)\n const seenSignatures = new Set<string>()\n\n // duplicate detection + per-tool aggregation\n for (const t of sortedTools) {\n byTool[t.toolName] ??= {\n calls: 0,\n callsWithCapturedArgs: 0,\n errors: 0,\n avgLatencyMs: 0,\n duplicates: 0,\n }\n const stat = byTool[t.toolName]!\n stat.calls += 1\n if (t.status === 'error') {\n stat.errors += 1\n totalErrors += 1\n }\n if (typeof t.latencyMs === 'number') stat.avgLatencyMs += t.latencyMs\n if (hasCapturedToolArgs(t)) {\n callsWithCapturedArgs += 1\n stat.callsWithCapturedArgs += 1\n const sig = `${t.toolName}|${argHash(t.args)}`\n if (seenSignatures.has(sig)) {\n stat.duplicates += 1\n totalDuplicates += 1\n }\n seenSignatures.add(sig)\n }\n }\n\n for (const stat of Object.values(byTool)) {\n stat.avgLatencyMs = stat.calls > 0 ? stat.avgLatencyMs / stat.calls : 0\n }\n\n // retry detection: per-tool chronological adjacency where error → next same-tool call\n let retryOpportunities = 0\n let retriesFollowed = 0\n for (const [, arr] of groupBy(sortedTools, (t) => t.toolName)) {\n for (let i = 0; i < arr.length; i++) {\n if (arr[i]!.status !== 'error') continue\n retryOpportunities += 1\n if (arr[i + 1]) retriesFollowed += 1\n }\n }\n const retryRate = retryOpportunities > 0 ? retriesFollowed / retryOpportunities : 0\n\n let selectionAccuracy: number | undefined\n if (options.selectionLabels) {\n const labeled = sortedTools.filter((t) => t.spanId in options.selectionLabels!)\n if (labeled.length > 0) {\n selectionAccuracy =\n labeled.filter((t) => options.selectionLabels![t.spanId]).length / labeled.length\n }\n }\n\n return {\n runId,\n totalCalls: sortedTools.length,\n callsWithCapturedArgs,\n byTool,\n errorRate: totalErrors / sortedTools.length,\n duplicateRate: callsWithCapturedArgs > 0 ? totalDuplicates / callsWithCapturedArgs : 0,\n retryRate,\n selectionAccuracy,\n }\n}\n\nexport type { Span }\n","/**\n * Baseline regression detection.\n *\n * Lifted from ADC baseline.ts. Every promotion-blocking signal boils down\n * to: \"is this run measurably worse than baseline?\" — with enough\n * statistical rigor to distinguish noise from drift.\n *\n * Uses:\n * - Welch's t-test (unequal variance) for per-metric mean comparison\n * - Cohen's d for effect size magnitude\n * - IQR for stability flag (unstable samples can't be trusted for comparisons)\n *\n * Returns a structured verdict: improved | regressed | stable | unstable.\n */\n\nimport { cohensD } from './statistics'\n\nexport interface MetricSamples {\n /** Stable metric key (e.g. \"overallScore\", \"firstTokenMs\"). */\n metric: string\n /** Whether higher values are better. */\n higherIsBetter: boolean\n baseline: number[]\n candidate: number[]\n}\n\nexport interface MetricVerdict {\n metric: string\n baselineMean: number\n candidateMean: number\n delta: number\n cohensD: number\n welchT: number\n welchDf: number\n welchP: number\n stable: boolean\n /** IQR of the combined samples — used as a rough stability indicator. */\n iqr: number\n verdict: 'improved' | 'regressed' | 'stable' | 'unstable'\n}\n\nexport interface BaselineReport {\n metrics: MetricVerdict[]\n /** True if any critical metric regressed. */\n hasRegression: boolean\n /** True if any metric is unstable (too noisy to judge). */\n hasUnstable: boolean\n}\n\nexport interface BaselineOptions {\n /** Effect size threshold for meaningful delta (default 0.5 — medium effect). */\n effectThreshold?: number\n /** p-value threshold for statistical significance (default 0.05). */\n alpha?: number\n /** IQR/mean ratio above which samples are flagged unstable (default 0.30). */\n unstableCvThreshold?: number\n}\n\n/**\n * Compare candidate samples against baseline per metric. Verdict logic:\n * - unstable: IQR/|mean| > threshold on either set — not enough signal\n * - improved: meaningful effect in the \"better\" direction AND p < alpha\n * - regressed: meaningful effect in the \"worse\" direction AND p < alpha\n * - stable: otherwise (no significant change)\n */\nexport function compareToBaseline(\n samples: MetricSamples[],\n options: BaselineOptions = {},\n): BaselineReport {\n const effectThreshold = options.effectThreshold ?? 0.5\n const alpha = options.alpha ?? 0.05\n const cvThreshold = options.unstableCvThreshold ?? 0.3\n\n const metrics: MetricVerdict[] = samples.map((s) => {\n if (s.baseline.length < 2 || s.candidate.length < 2) {\n throw new Error(`compareToBaseline: need ≥2 samples per side for \"${s.metric}\"`)\n }\n const bMean = mean(s.baseline)\n const cMean = mean(s.candidate)\n const delta = cMean - bMean\n const d = cohensD(s.baseline, s.candidate) // positive = candidate higher\n const { t, df, p } = welchsTTest(s.baseline, s.candidate)\n // Stability is per-side: a comparison is trustworthy only when BOTH\n // samples are internally consistent. Combining the sides would flag\n // large-but-real deltas as \"unstable\" which is exactly what we want\n // to detect.\n const baselineIqr = iqr(s.baseline)\n const candidateIqr = iqr(s.candidate)\n const baselineStable = baselineIqr / Math.max(Math.abs(bMean), 1e-9) <= cvThreshold\n const candidateStable = candidateIqr / Math.max(Math.abs(cMean), 1e-9) <= cvThreshold\n const stable = baselineStable && candidateStable\n const reportedIqr = Math.max(baselineIqr, candidateIqr)\n\n let verdict: MetricVerdict['verdict']\n if (!stable) {\n verdict = 'unstable'\n } else if (p < alpha && Math.abs(d) >= effectThreshold) {\n const candidateIsBetter = s.higherIsBetter ? delta > 0 : delta < 0\n verdict = candidateIsBetter ? 'improved' : 'regressed'\n } else {\n verdict = 'stable'\n }\n\n return {\n metric: s.metric,\n baselineMean: bMean,\n candidateMean: cMean,\n delta,\n cohensD: d,\n welchT: t,\n welchDf: df,\n welchP: p,\n stable,\n iqr: reportedIqr,\n verdict,\n }\n })\n\n return {\n metrics,\n hasRegression: metrics.some((m) => m.verdict === 'regressed'),\n hasUnstable: metrics.some((m) => m.verdict === 'unstable'),\n }\n}\n\nfunction mean(xs: number[]): number {\n return xs.reduce((a, b) => a + b, 0) / xs.length\n}\n\n/** Inter-quartile range; 0 when the sample has no spread. */\nexport function iqr(xs: number[]): number {\n if (xs.length === 0) return 0\n const sorted = [...xs].sort((a, b) => a - b)\n const q = (p: number) => {\n const idx = p * (sorted.length - 1)\n const lo = Math.floor(idx)\n const hi = Math.ceil(idx)\n return sorted[lo]! + (sorted[hi]! - sorted[lo]!) * (idx - lo)\n }\n return q(0.75) - q(0.25)\n}\n\n/**\n * Welch's t-test — unequal-variance two-sample t. Uses the same Student-t\n * CDF as `pairedTTest` (via incomplete beta); falls back to normal tail\n * when df is large.\n */\nexport function welchsTTest(a: number[], b: number[]): { t: number; df: number; p: number } {\n if (a.length < 2 || b.length < 2) return { t: 0, df: 0, p: 1 }\n const mA = mean(a)\n const mB = mean(b)\n const vA = variance(a, mA)\n const vB = variance(b, mB)\n const seSquared = vA / a.length + vB / b.length\n if (seSquared === 0) return { t: mA === mB ? 0 : Infinity, df: 0, p: mA === mB ? 1 : 0 }\n const t = (mB - mA) / Math.sqrt(seSquared)\n const df =\n (seSquared * seSquared) /\n ((vA / a.length) ** 2 / (a.length - 1) + (vB / b.length) ** 2 / (b.length - 1))\n const p = 2 * (1 - studentTCdf(Math.abs(t), df))\n return { t, df, p }\n}\n\nfunction variance(xs: number[], m: number): number {\n return xs.reduce((acc, x) => acc + (x - m) ** 2, 0) / (xs.length - 1)\n}\n\n// Re-used from statistics.ts via small local copy to avoid exporting internals.\nfunction studentTCdf(t: number, df: number): number {\n if (df <= 0) return 0.5\n if (df > 100) return normalCdf(t)\n const x = df / (df + t * t)\n const ib = incompleteBeta(x, df / 2, 0.5)\n return t >= 0 ? 1 - 0.5 * ib : 0.5 * ib\n}\n\nfunction incompleteBeta(x: number, a: number, b: number): number {\n if (x <= 0) return 0\n if (x >= 1) return 1\n const lnBeta = lnGamma(a) + lnGamma(b) - lnGamma(a + b)\n const front = Math.exp(Math.log(x) * a + Math.log(1 - x) * b - lnBeta) / a\n let c = 1\n let d = 1 - ((a + b) * x) / (a + 1)\n if (Math.abs(d) < 1e-30) d = 1e-30\n d = 1 / d\n let f = d\n for (let m = 1; m <= 200; m++) {\n const m2 = 2 * m\n let num = (m * (b - m) * x) / ((a + m2 - 1) * (a + m2))\n d = 1 + num * d\n if (Math.abs(d) < 1e-30) d = 1e-30\n c = 1 + num / c\n if (Math.abs(c) < 1e-30) c = 1e-30\n d = 1 / d\n f *= d * c\n num = -((a + m) * (a + b + m) * x) / ((a + m2) * (a + m2 + 1))\n d = 1 + num * d\n if (Math.abs(d) < 1e-30) d = 1e-30\n c = 1 + num / c\n if (Math.abs(c) < 1e-30) c = 1e-30\n d = 1 / d\n const delta = d * c\n f *= delta\n if (Math.abs(delta - 1) < 3e-7) break\n }\n return front * f\n}\n\nfunction lnGamma(z: number): number {\n const coefs = [\n 0.99999999999980993, 676.5203681218851, -1259.1392167224028, 771.32342877765313,\n -176.61502916214059, 12.507343278686905, -0.13857109526572012, 9.9843695780195716e-6,\n 1.5056327351493116e-7,\n ]\n if (z < 0.5) return Math.log(Math.PI / Math.sin(Math.PI * z)) - lnGamma(1 - z)\n z -= 1\n let x = coefs[0]!\n for (let i = 1; i < 9; i++) x += coefs[i]! / (z + i)\n const t = z + 7.5\n return 0.5 * Math.log(2 * Math.PI) + (z + 0.5) * Math.log(t) - t + Math.log(x)\n}\n\nfunction normalCdf(x: number): number {\n const a1 = 0.254829592\n const a2 = -0.284496736\n const a3 = 1.421413741\n const a4 = -1.453152027\n const a5 = 1.061405429\n const p = 0.3275911\n const sign = x < 0 ? -1 : 1\n const absX = Math.abs(x)\n const t = 1 / (1 + p * absX)\n const y = 1 - ((((a5 * t + a4) * t + a3) * t + a2) * t + a1) * t * Math.exp((-absX * absX) / 2)\n return 0.5 * (1 + sign * y)\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
// src/trajectory.ts
|
|
2
|
+
async function buildTrajectory(store, runId) {
|
|
3
|
+
const spans = await store.spans({ runId });
|
|
4
|
+
const events = await store.events({ runId });
|
|
5
|
+
const childrenOf = /* @__PURE__ */ new Map();
|
|
6
|
+
for (const s of spans) {
|
|
7
|
+
const arr = childrenOf.get(s.parentSpanId) ?? [];
|
|
8
|
+
arr.push(s);
|
|
9
|
+
childrenOf.set(s.parentSpanId, arr);
|
|
10
|
+
}
|
|
11
|
+
for (const arr of childrenOf.values()) arr.sort((a, b) => a.startedAt - b.startedAt);
|
|
12
|
+
const eventsBySpan = /* @__PURE__ */ new Map();
|
|
13
|
+
for (const e of events) {
|
|
14
|
+
if (!e.spanId) continue;
|
|
15
|
+
const arr = eventsBySpan.get(e.spanId) ?? [];
|
|
16
|
+
arr.push(e);
|
|
17
|
+
eventsBySpan.set(e.spanId, arr);
|
|
18
|
+
}
|
|
19
|
+
const steps = [];
|
|
20
|
+
const walk = (spanId, depth) => {
|
|
21
|
+
const kids = childrenOf.get(spanId) ?? [];
|
|
22
|
+
for (const child of kids) {
|
|
23
|
+
steps.push({
|
|
24
|
+
index: steps.length,
|
|
25
|
+
span: child,
|
|
26
|
+
depth,
|
|
27
|
+
events: eventsBySpan.get(child.spanId) ?? []
|
|
28
|
+
});
|
|
29
|
+
walk(child.spanId, depth + 1);
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
walk(void 0, 0);
|
|
33
|
+
const llmTurns = steps.filter((s) => s.span.kind === "llm").length;
|
|
34
|
+
const toolCalls = steps.filter((s) => s.span.kind === "tool").length;
|
|
35
|
+
const judgeVerdicts = steps.filter((s) => s.span.kind === "judge").length;
|
|
36
|
+
const retrievals = steps.filter((s) => s.span.kind === "retrieval").length;
|
|
37
|
+
let totalDurationMs = 0;
|
|
38
|
+
if (steps.length > 0) {
|
|
39
|
+
const starts = spans.map((s) => s.startedAt);
|
|
40
|
+
const ends = spans.map((s) => s.endedAt ?? s.startedAt);
|
|
41
|
+
totalDurationMs = Math.max(...ends) - Math.min(...starts);
|
|
42
|
+
}
|
|
43
|
+
return { runId, steps, llmTurns, toolCalls, judgeVerdicts, retrievals, totalDurationMs };
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export {
|
|
47
|
+
buildTrajectory
|
|
48
|
+
};
|
|
49
|
+
//# sourceMappingURL=chunk-RZTMDUO7.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/trajectory.ts"],"sourcesContent":["/**\n * Trajectory — ordered, structured view over a run's spans.\n *\n * A pure function `buildTrajectory(store, runId) → Trajectory` returns\n * a topologically ordered list of `TrajectoryStep` with parent-child\n * grouping collapsed into a single line-of-agent-work. Separate\n * analyzers (stuck-loop detection, waste ratio) live in\n * `pipelines/` and consume the trajectory.\n */\n\nimport type { Span, TraceEvent } from './trace/schema'\nimport type { TraceStore } from './trace/store'\n\nexport interface TrajectoryStep {\n index: number\n span: Span\n /** Depth in the span tree from the root. 0 = top-level. */\n depth: number\n /** Events attached to this span. */\n events: TraceEvent[]\n}\n\nexport interface Trajectory {\n runId: string\n steps: TrajectoryStep[]\n llmTurns: number\n toolCalls: number\n judgeVerdicts: number\n retrievals: number\n totalDurationMs: number\n}\n\nexport async function buildTrajectory(store: TraceStore, runId: string): Promise<Trajectory> {\n const spans = await store.spans({ runId })\n const events = await store.events({ runId })\n const childrenOf = new Map<string | undefined, Span[]>()\n for (const s of spans) {\n const arr = childrenOf.get(s.parentSpanId) ?? []\n arr.push(s)\n childrenOf.set(s.parentSpanId, arr)\n }\n // Sort children by startedAt so DFS yields chronological order within siblings.\n for (const arr of childrenOf.values()) arr.sort((a, b) => a.startedAt - b.startedAt)\n\n const eventsBySpan = new Map<string, TraceEvent[]>()\n for (const e of events) {\n if (!e.spanId) continue\n const arr = eventsBySpan.get(e.spanId) ?? []\n arr.push(e)\n eventsBySpan.set(e.spanId, arr)\n }\n\n const steps: TrajectoryStep[] = []\n const walk = (spanId: string | undefined, depth: number): void => {\n const kids = childrenOf.get(spanId) ?? []\n for (const child of kids) {\n steps.push({\n index: steps.length,\n span: child,\n depth,\n events: eventsBySpan.get(child.spanId) ?? [],\n })\n walk(child.spanId, depth + 1)\n }\n }\n walk(undefined, 0)\n\n const llmTurns = steps.filter((s) => s.span.kind === 'llm').length\n const toolCalls = steps.filter((s) => s.span.kind === 'tool').length\n const judgeVerdicts = steps.filter((s) => s.span.kind === 'judge').length\n const retrievals = steps.filter((s) => s.span.kind === 'retrieval').length\n\n let totalDurationMs = 0\n if (steps.length > 0) {\n const starts = spans.map((s) => s.startedAt)\n const ends = spans.map((s) => s.endedAt ?? s.startedAt)\n totalDurationMs = Math.max(...ends) - Math.min(...starts)\n }\n\n return { runId, steps, llmTurns, toolCalls, judgeVerdicts, retrievals, totalDurationMs }\n}\n\n// Re-export core types for convenience so consumers don't import from two paths.\nexport type { Span, TraceEvent } from './trace/schema'\nexport type { TraceStore } from './trace/store'\n"],"mappings":";AAgCA,eAAsB,gBAAgB,OAAmB,OAAoC;AAC3F,QAAM,QAAQ,MAAM,MAAM,MAAM,EAAE,MAAM,CAAC;AACzC,QAAM,SAAS,MAAM,MAAM,OAAO,EAAE,MAAM,CAAC;AAC3C,QAAM,aAAa,oBAAI,IAAgC;AACvD,aAAW,KAAK,OAAO;AACrB,UAAM,MAAM,WAAW,IAAI,EAAE,YAAY,KAAK,CAAC;AAC/C,QAAI,KAAK,CAAC;AACV,eAAW,IAAI,EAAE,cAAc,GAAG;AAAA,EACpC;AAEA,aAAW,OAAO,WAAW,OAAO,EAAG,KAAI,KAAK,CAAC,GAAG,MAAM,EAAE,YAAY,EAAE,SAAS;AAEnF,QAAM,eAAe,oBAAI,IAA0B;AACnD,aAAW,KAAK,QAAQ;AACtB,QAAI,CAAC,EAAE,OAAQ;AACf,UAAM,MAAM,aAAa,IAAI,EAAE,MAAM,KAAK,CAAC;AAC3C,QAAI,KAAK,CAAC;AACV,iBAAa,IAAI,EAAE,QAAQ,GAAG;AAAA,EAChC;AAEA,QAAM,QAA0B,CAAC;AACjC,QAAM,OAAO,CAAC,QAA4B,UAAwB;AAChE,UAAM,OAAO,WAAW,IAAI,MAAM,KAAK,CAAC;AACxC,eAAW,SAAS,MAAM;AACxB,YAAM,KAAK;AAAA,QACT,OAAO,MAAM;AAAA,QACb,MAAM;AAAA,QACN;AAAA,QACA,QAAQ,aAAa,IAAI,MAAM,MAAM,KAAK,CAAC;AAAA,MAC7C,CAAC;AACD,WAAK,MAAM,QAAQ,QAAQ,CAAC;AAAA,IAC9B;AAAA,EACF;AACA,OAAK,QAAW,CAAC;AAEjB,QAAM,WAAW,MAAM,OAAO,CAAC,MAAM,EAAE,KAAK,SAAS,KAAK,EAAE;AAC5D,QAAM,YAAY,MAAM,OAAO,CAAC,MAAM,EAAE,KAAK,SAAS,MAAM,EAAE;AAC9D,QAAM,gBAAgB,MAAM,OAAO,CAAC,MAAM,EAAE,KAAK,SAAS,OAAO,EAAE;AACnE,QAAM,aAAa,MAAM,OAAO,CAAC,MAAM,EAAE,KAAK,SAAS,WAAW,EAAE;AAEpE,MAAI,kBAAkB;AACtB,MAAI,MAAM,SAAS,GAAG;AACpB,UAAM,SAAS,MAAM,IAAI,CAAC,MAAM,EAAE,SAAS;AAC3C,UAAM,OAAO,MAAM,IAAI,CAAC,MAAM,EAAE,WAAW,EAAE,SAAS;AACtD,sBAAkB,KAAK,IAAI,GAAG,IAAI,IAAI,KAAK,IAAI,GAAG,MAAM;AAAA,EAC1D;AAEA,SAAO,EAAE,OAAO,OAAO,UAAU,WAAW,eAAe,YAAY,gBAAgB;AACzF;","names":[]}
|
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
// src/rollout/readers/claude-jsonl.ts
|
|
2
|
+
import { readdir, readFile } from "fs/promises";
|
|
3
|
+
import { homedir } from "os";
|
|
4
|
+
import { join } from "path";
|
|
5
|
+
var DEFAULT_CLAUDE_PROJECTS_DIR = join(homedir(), ".claude", "projects");
|
|
6
|
+
function claudeProjectSlug(cwd) {
|
|
7
|
+
return cwd.replace(/[^a-zA-Z0-9-]/g, "-");
|
|
8
|
+
}
|
|
9
|
+
async function findClaudeTranscripts(cwd, projectsDir = DEFAULT_CLAUDE_PROJECTS_DIR) {
|
|
10
|
+
const dir = join(projectsDir, claudeProjectSlug(cwd));
|
|
11
|
+
const names = await readdir(dir).catch(() => []);
|
|
12
|
+
return names.filter((n) => n.endsWith(".jsonl")).sort().map((n) => ({ sessionId: n.replace(/\.jsonl$/, ""), path: join(dir, n) }));
|
|
13
|
+
}
|
|
14
|
+
var isRecord = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
15
|
+
function parseClaudeEntries(raw) {
|
|
16
|
+
const out = [];
|
|
17
|
+
for (const line of raw.split("\n")) {
|
|
18
|
+
if (!line.trim()) continue;
|
|
19
|
+
let entry;
|
|
20
|
+
try {
|
|
21
|
+
const parsed = JSON.parse(line);
|
|
22
|
+
if (!isRecord(parsed)) continue;
|
|
23
|
+
entry = parsed;
|
|
24
|
+
} catch {
|
|
25
|
+
continue;
|
|
26
|
+
}
|
|
27
|
+
if (entry.type !== "user" && entry.type !== "assistant") continue;
|
|
28
|
+
const message = entry.message;
|
|
29
|
+
if (!isRecord(message)) continue;
|
|
30
|
+
out.push({
|
|
31
|
+
type: entry.type,
|
|
32
|
+
timestamp: typeof entry.timestamp === "string" ? entry.timestamp : null,
|
|
33
|
+
message,
|
|
34
|
+
toolUseResult: entry.toolUseResult,
|
|
35
|
+
isSidechain: entry.isSidechain === true,
|
|
36
|
+
agentId: typeof entry.agentId === "string" ? entry.agentId : null
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
return out;
|
|
40
|
+
}
|
|
41
|
+
function blockText(content) {
|
|
42
|
+
if (typeof content === "string") return content;
|
|
43
|
+
if (!Array.isArray(content)) return "";
|
|
44
|
+
return content.filter(
|
|
45
|
+
(b) => isRecord(b) && b.type === "text" && typeof b.text === "string"
|
|
46
|
+
).map((b) => b.text).join("\n");
|
|
47
|
+
}
|
|
48
|
+
async function readClaudeTranscript(path, options = {}) {
|
|
49
|
+
return transcriptFromEntries(parseClaudeEntries(await readFile(path, "utf8")), options);
|
|
50
|
+
}
|
|
51
|
+
function transcriptFromEntries(entries, options = {}) {
|
|
52
|
+
const wantSidechain = options.includeSidechain === true;
|
|
53
|
+
const messages = [];
|
|
54
|
+
const usage = { tokensIn: 0, tokensOut: 0, cacheRead: 0, cacheWrite: 0 };
|
|
55
|
+
let startedAt = null;
|
|
56
|
+
let endedAt = null;
|
|
57
|
+
let model = null;
|
|
58
|
+
let lastAssistantApiId = null;
|
|
59
|
+
let lastAssistantIndex = -1;
|
|
60
|
+
for (const entry of entries) {
|
|
61
|
+
if (entry.isSidechain !== wantSidechain) continue;
|
|
62
|
+
const message = entry.message;
|
|
63
|
+
if (entry.timestamp !== null) {
|
|
64
|
+
if (startedAt === null) startedAt = entry.timestamp;
|
|
65
|
+
endedAt = entry.timestamp;
|
|
66
|
+
}
|
|
67
|
+
if (entry.type === "user") {
|
|
68
|
+
lastAssistantApiId = null;
|
|
69
|
+
lastAssistantIndex = -1;
|
|
70
|
+
const content2 = message.content;
|
|
71
|
+
if (typeof content2 === "string") {
|
|
72
|
+
messages.push({ role: "user", content: content2 });
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
if (!Array.isArray(content2)) continue;
|
|
76
|
+
let userText = "";
|
|
77
|
+
for (const block of content2) {
|
|
78
|
+
if (!isRecord(block)) continue;
|
|
79
|
+
if (block.type === "tool_result" && typeof block.tool_use_id === "string") {
|
|
80
|
+
messages.push({
|
|
81
|
+
role: "tool",
|
|
82
|
+
tool_call_id: block.tool_use_id,
|
|
83
|
+
content: blockText(block.content) || (typeof block.content === "string" ? block.content : "")
|
|
84
|
+
});
|
|
85
|
+
} else if (block.type === "text" && typeof block.text === "string") {
|
|
86
|
+
userText += (userText.length > 0 ? "\n" : "") + block.text;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
if (userText.length > 0) messages.push({ role: "user", content: userText });
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
if (typeof message.model === "string") model = message.model;
|
|
93
|
+
const apiId = typeof message.id === "string" ? message.id : null;
|
|
94
|
+
const continuesTurn = apiId !== null && apiId === lastAssistantApiId && lastAssistantIndex >= 0;
|
|
95
|
+
const msgUsage = message.usage;
|
|
96
|
+
if (isRecord(msgUsage) && !continuesTurn) {
|
|
97
|
+
usage.tokensIn += typeof msgUsage.input_tokens === "number" ? msgUsage.input_tokens : 0;
|
|
98
|
+
usage.tokensOut += typeof msgUsage.output_tokens === "number" ? msgUsage.output_tokens : 0;
|
|
99
|
+
usage.cacheRead += typeof msgUsage.cache_read_input_tokens === "number" ? msgUsage.cache_read_input_tokens : 0;
|
|
100
|
+
usage.cacheWrite += typeof msgUsage.cache_creation_input_tokens === "number" ? msgUsage.cache_creation_input_tokens : 0;
|
|
101
|
+
}
|
|
102
|
+
const content = message.content;
|
|
103
|
+
if (!Array.isArray(content)) continue;
|
|
104
|
+
let reasoning = "";
|
|
105
|
+
let text = "";
|
|
106
|
+
const toolCalls = [];
|
|
107
|
+
for (const block of content) {
|
|
108
|
+
if (!isRecord(block)) continue;
|
|
109
|
+
if (block.type === "thinking" && typeof block.thinking === "string" && block.thinking.length > 0) {
|
|
110
|
+
reasoning += (reasoning.length > 0 ? "\n" : "") + block.thinking;
|
|
111
|
+
} else if (block.type === "text" && typeof block.text === "string") {
|
|
112
|
+
text += (text.length > 0 ? "\n" : "") + block.text;
|
|
113
|
+
} else if (block.type === "tool_use" && typeof block.id === "string") {
|
|
114
|
+
toolCalls.push({
|
|
115
|
+
id: block.id,
|
|
116
|
+
type: "function",
|
|
117
|
+
function: {
|
|
118
|
+
name: typeof block.name === "string" ? block.name : "unknown",
|
|
119
|
+
arguments: JSON.stringify(block.input ?? {})
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
if (reasoning.length === 0 && text.length === 0 && toolCalls.length === 0) continue;
|
|
125
|
+
if (continuesTurn) {
|
|
126
|
+
const prev = messages[lastAssistantIndex];
|
|
127
|
+
if (text.length > 0) prev.content = prev.content === null ? text : `${prev.content}
|
|
128
|
+
${text}`;
|
|
129
|
+
if (reasoning.length > 0) {
|
|
130
|
+
prev.reasoning_content = prev.reasoning_content === void 0 ? reasoning : `${prev.reasoning_content}
|
|
131
|
+
${reasoning}`;
|
|
132
|
+
}
|
|
133
|
+
if (toolCalls.length > 0) prev.tool_calls = [...prev.tool_calls ?? [], ...toolCalls];
|
|
134
|
+
continue;
|
|
135
|
+
}
|
|
136
|
+
messages.push({
|
|
137
|
+
role: "assistant",
|
|
138
|
+
content: text.length > 0 ? text : null,
|
|
139
|
+
...reasoning.length > 0 ? { reasoning_content: reasoning } : {},
|
|
140
|
+
...toolCalls.length > 0 ? { tool_calls: toolCalls } : {}
|
|
141
|
+
});
|
|
142
|
+
lastAssistantApiId = apiId;
|
|
143
|
+
lastAssistantIndex = messages.length - 1;
|
|
144
|
+
}
|
|
145
|
+
return { messages, usage, startedAt, endedAt, model };
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// src/rollout/readers/opencode-sqlite.ts
|
|
149
|
+
import { homedir as homedir2 } from "os";
|
|
150
|
+
import { join as join2 } from "path";
|
|
151
|
+
var DEFAULT_OPENCODE_DB = join2(homedir2(), ".local", "share", "opencode", "opencode.db");
|
|
152
|
+
var NODE_SQLITE_SPECIFIER = ["node", "sqlite"].join(":");
|
|
153
|
+
async function openOpencodeDb(path = DEFAULT_OPENCODE_DB) {
|
|
154
|
+
try {
|
|
155
|
+
const { DatabaseSync } = await import(
|
|
156
|
+
/* @vite-ignore */
|
|
157
|
+
NODE_SQLITE_SPECIFIER
|
|
158
|
+
);
|
|
159
|
+
const db = new DatabaseSync(path, { readOnly: true });
|
|
160
|
+
db.prepare("SELECT id FROM session LIMIT 1").get();
|
|
161
|
+
return db;
|
|
162
|
+
} catch {
|
|
163
|
+
return null;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
var isRecord2 = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
167
|
+
function parseSessionRow(row) {
|
|
168
|
+
let model = null;
|
|
169
|
+
if (typeof row.model === "string" && row.model.length > 0) {
|
|
170
|
+
try {
|
|
171
|
+
const parsed = JSON.parse(row.model);
|
|
172
|
+
if (isRecord2(parsed)) model = parsed;
|
|
173
|
+
} catch {
|
|
174
|
+
model = null;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
return {
|
|
178
|
+
id: String(row.id),
|
|
179
|
+
parentId: row.parent_id === null || row.parent_id === void 0 ? null : String(row.parent_id),
|
|
180
|
+
directory: String(row.directory),
|
|
181
|
+
agent: row.agent === null || row.agent === void 0 ? null : String(row.agent),
|
|
182
|
+
model,
|
|
183
|
+
costUsd: Number(row.cost ?? 0),
|
|
184
|
+
tokensInput: Number(row.tokens_input ?? 0),
|
|
185
|
+
tokensOutput: Number(row.tokens_output ?? 0),
|
|
186
|
+
tokensReasoning: Number(row.tokens_reasoning ?? 0),
|
|
187
|
+
tokensCacheRead: Number(row.tokens_cache_read ?? 0),
|
|
188
|
+
tokensCacheWrite: Number(row.tokens_cache_write ?? 0),
|
|
189
|
+
timeCreated: Number(row.time_created ?? 0),
|
|
190
|
+
timeUpdated: Number(row.time_updated ?? 0)
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
var SESSION_COLUMNS = "id, parent_id, directory, agent, model, cost, tokens_input, tokens_output, tokens_reasoning, tokens_cache_read, tokens_cache_write, time_created, time_updated";
|
|
194
|
+
function findOpencodeSessionsByDirectory(db, directory) {
|
|
195
|
+
const rows = db.prepare(`SELECT ${SESSION_COLUMNS} FROM session WHERE directory = ? ORDER BY time_created`).all(directory);
|
|
196
|
+
return rows.map(parseSessionRow);
|
|
197
|
+
}
|
|
198
|
+
function findOpencodeSessionById(db, sessionId) {
|
|
199
|
+
const row = db.prepare(`SELECT ${SESSION_COLUMNS} FROM session WHERE id = ?`).get(sessionId);
|
|
200
|
+
return row === void 0 ? null : parseSessionRow(row);
|
|
201
|
+
}
|
|
202
|
+
function toolResultContent(output) {
|
|
203
|
+
if (typeof output === "string") return output;
|
|
204
|
+
if (output === null || output === void 0) return "";
|
|
205
|
+
return JSON.stringify(output);
|
|
206
|
+
}
|
|
207
|
+
function readOpencodeSessionMessages(db, sessionId) {
|
|
208
|
+
const messageRows = db.prepare("SELECT id, data FROM message WHERE session_id = ? ORDER BY time_created, id").all(sessionId);
|
|
209
|
+
const partsStmt = db.prepare("SELECT data FROM part WHERE message_id = ? ORDER BY id");
|
|
210
|
+
const messages = [];
|
|
211
|
+
for (const messageRow of messageRows) {
|
|
212
|
+
let data;
|
|
213
|
+
try {
|
|
214
|
+
const parsed = JSON.parse(messageRow.data);
|
|
215
|
+
if (!isRecord2(parsed)) continue;
|
|
216
|
+
data = parsed;
|
|
217
|
+
} catch {
|
|
218
|
+
continue;
|
|
219
|
+
}
|
|
220
|
+
const parts = [];
|
|
221
|
+
for (const row of partsStmt.all(messageRow.id)) {
|
|
222
|
+
try {
|
|
223
|
+
const parsed = JSON.parse(row.data);
|
|
224
|
+
if (isRecord2(parsed)) parts.push(parsed);
|
|
225
|
+
} catch {
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
if (data.role === "user") {
|
|
229
|
+
const text = parts.filter((p) => p.type === "text" && typeof p.text === "string").map((p) => p.text).join("\n");
|
|
230
|
+
messages.push({ role: "user", content: text });
|
|
231
|
+
continue;
|
|
232
|
+
}
|
|
233
|
+
if (data.role !== "assistant") continue;
|
|
234
|
+
const steps = [];
|
|
235
|
+
let current = [];
|
|
236
|
+
for (const part of parts) {
|
|
237
|
+
if (part.type === "step-start") {
|
|
238
|
+
if (current.length > 0) steps.push(current);
|
|
239
|
+
current = [];
|
|
240
|
+
continue;
|
|
241
|
+
}
|
|
242
|
+
if (part.type === "step-finish" || part.type === "snapshot" || part.type === "patch") continue;
|
|
243
|
+
current.push(part);
|
|
244
|
+
}
|
|
245
|
+
if (current.length > 0) steps.push(current);
|
|
246
|
+
for (const step of steps) {
|
|
247
|
+
const reasoning = step.filter((p) => p.type === "reasoning" && typeof p.text === "string" && p.text.length > 0).map((p) => p.text).join("\n");
|
|
248
|
+
const text = step.filter((p) => p.type === "text" && typeof p.text === "string").map((p) => p.text).join("\n");
|
|
249
|
+
const toolParts = step.filter((p) => p.type === "tool" && typeof p.callID === "string");
|
|
250
|
+
const toolCalls = toolParts.map((p) => ({
|
|
251
|
+
id: p.callID,
|
|
252
|
+
type: "function",
|
|
253
|
+
function: {
|
|
254
|
+
name: p.tool ?? "unknown",
|
|
255
|
+
arguments: JSON.stringify(p.state?.input ?? {})
|
|
256
|
+
}
|
|
257
|
+
}));
|
|
258
|
+
if (reasoning.length === 0 && text.length === 0 && toolCalls.length === 0) continue;
|
|
259
|
+
messages.push({
|
|
260
|
+
role: "assistant",
|
|
261
|
+
content: text.length > 0 ? text : null,
|
|
262
|
+
...reasoning.length > 0 ? { reasoning_content: reasoning } : {},
|
|
263
|
+
...toolCalls.length > 0 ? { tool_calls: toolCalls } : {}
|
|
264
|
+
});
|
|
265
|
+
for (const p of toolParts) {
|
|
266
|
+
messages.push({
|
|
267
|
+
role: "tool",
|
|
268
|
+
tool_call_id: p.callID,
|
|
269
|
+
name: p.tool ?? "unknown",
|
|
270
|
+
content: toolResultContent(p.state?.output)
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
return messages;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
export {
|
|
279
|
+
DEFAULT_CLAUDE_PROJECTS_DIR,
|
|
280
|
+
claudeProjectSlug,
|
|
281
|
+
findClaudeTranscripts,
|
|
282
|
+
parseClaudeEntries,
|
|
283
|
+
readClaudeTranscript,
|
|
284
|
+
transcriptFromEntries,
|
|
285
|
+
DEFAULT_OPENCODE_DB,
|
|
286
|
+
openOpencodeDb,
|
|
287
|
+
findOpencodeSessionsByDirectory,
|
|
288
|
+
findOpencodeSessionById,
|
|
289
|
+
readOpencodeSessionMessages
|
|
290
|
+
};
|
|
291
|
+
//# sourceMappingURL=chunk-VBQ3CRKH.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/rollout/readers/claude-jsonl.ts","../src/rollout/readers/opencode-sqlite.ts"],"sourcesContent":["/**\n * Backfill reader over Claude Code project transcripts\n * (~/.claude/projects/<cwd-slug>/<sessionId>.jsonl) → canonical\n * chat-with-tools messages plus per-session token usage.\n *\n * Transcript lines consumed: type:\"user\" (string content or content blocks —\n * text + tool_result) and type:\"assistant\" (content blocks — thinking, text,\n * tool_use; message.usage carries tokens). Sidechain lines (isSidechain=true,\n * subagent threads) are separate invocations and are excluded from the main\n * transcript. Everything else (queue-operation, attachment, last-prompt…) is\n * transport metadata, not conversation.\n */\n\nimport { readdir, readFile } from 'node:fs/promises'\nimport { homedir } from 'node:os'\nimport { join } from 'node:path'\nimport type { ChatMessage, ChatToolCall } from '../schema'\n\nexport const DEFAULT_CLAUDE_PROJECTS_DIR = join(homedir(), '.claude', 'projects')\n\n/** Claude Code's project-directory slug for a working directory. */\nexport function claudeProjectSlug(cwd: string): string {\n return cwd.replace(/[^a-zA-Z0-9-]/g, '-')\n}\n\nexport interface ClaudeTranscriptRef {\n sessionId: string\n path: string\n}\n\n/** Transcript files recorded for sessions launched from `cwd`. */\nexport async function findClaudeTranscripts(\n cwd: string,\n projectsDir: string = DEFAULT_CLAUDE_PROJECTS_DIR,\n): Promise<ClaudeTranscriptRef[]> {\n const dir = join(projectsDir, claudeProjectSlug(cwd))\n const names = await readdir(dir).catch(() => [])\n return names\n .filter((n) => n.endsWith('.jsonl'))\n .sort()\n .map((n) => ({ sessionId: n.replace(/\\.jsonl$/, ''), path: join(dir, n) }))\n}\n\nexport interface ClaudeUsageTotals {\n tokensIn: number\n tokensOut: number\n cacheRead: number\n cacheWrite: number\n}\n\nexport interface ClaudeTranscript {\n messages: ChatMessage[]\n usage: ClaudeUsageTotals\n /** Timestamp of the first conversation line; null = empty transcript. */\n startedAt: string | null\n endedAt: string | null\n model: string | null\n}\n\nconst isRecord = (v: unknown): v is Record<string, unknown> =>\n typeof v === 'object' && v !== null && !Array.isArray(v)\n\n/**\n * One conversation line of a transcript, still in Claude Code's own shape.\n *\n * This is the single line-level parse of the format. `readClaudeTranscript`\n * projects it to canonical messages + usage; the supervision-tree reader\n * (`src/supervisor-run/claude-code-reader.ts`) projects the SAME entries to\n * spawn/settle/steer instants. Two projections, one parser — a second\n * transcript parser is how the two views silently disagree.\n */\nexport interface ClaudeEntry {\n readonly type: 'user' | 'assistant'\n /** ISO instant of the line; null when the line carried none. */\n readonly timestamp: string | null\n /** The Anthropic message body (`role`, `content`, `model`, `usage`). */\n readonly message: Record<string, unknown>\n /** Claude Code's structured tool result, when the line carries one. */\n readonly toolUseResult: unknown\n /** True on subagent threads — a separate invocation, not this transcript's turn. */\n readonly isSidechain: boolean\n /** Subagent id Claude Code stamps on sidechain lines; null on main-thread lines. */\n readonly agentId: string | null\n}\n\n/** Parse transcript jsonl text into conversation lines. Non-conversation lines are dropped. */\nexport function parseClaudeEntries(raw: string): ClaudeEntry[] {\n const out: ClaudeEntry[] = []\n for (const line of raw.split('\\n')) {\n if (!line.trim()) continue\n let entry: Record<string, unknown>\n try {\n const parsed: unknown = JSON.parse(line)\n if (!isRecord(parsed)) continue\n entry = parsed\n } catch {\n continue\n }\n if (entry.type !== 'user' && entry.type !== 'assistant') continue\n const message = entry.message\n if (!isRecord(message)) continue\n out.push({\n type: entry.type,\n timestamp: typeof entry.timestamp === 'string' ? entry.timestamp : null,\n message,\n toolUseResult: entry.toolUseResult,\n isSidechain: entry.isSidechain === true,\n agentId: typeof entry.agentId === 'string' ? entry.agentId : null,\n })\n }\n return out\n}\n\nexport interface ReadClaudeTranscriptOptions {\n /**\n * Read the sidechain (subagent) thread instead of skipping it. Subagent\n * transcripts under `<session>/subagents/agent-<id>.jsonl` are sidechain\n * lines end to end, so their usage is invisible without this.\n */\n readonly includeSidechain?: boolean\n}\n\nfunction blockText(content: unknown): string {\n if (typeof content === 'string') return content\n if (!Array.isArray(content)) return ''\n return content\n .filter(\n (b): b is Record<string, unknown> =>\n isRecord(b) && b.type === 'text' && typeof b.text === 'string',\n )\n .map((b) => b.text as string)\n .join('\\n')\n}\n\n/** Parse one transcript jsonl into canonical messages + usage totals. */\nexport async function readClaudeTranscript(\n path: string,\n options: ReadClaudeTranscriptOptions = {},\n): Promise<ClaudeTranscript> {\n return transcriptFromEntries(parseClaudeEntries(await readFile(path, 'utf8')), options)\n}\n\n/** The messages+usage projection of already-parsed entries. */\nexport function transcriptFromEntries(\n entries: readonly ClaudeEntry[],\n options: ReadClaudeTranscriptOptions = {},\n): ClaudeTranscript {\n const wantSidechain = options.includeSidechain === true\n const messages: ChatMessage[] = []\n const usage: ClaudeUsageTotals = { tokensIn: 0, tokensOut: 0, cacheRead: 0, cacheWrite: 0 }\n let startedAt: string | null = null\n let endedAt: string | null = null\n let model: string | null = null\n // Claude Code writes one jsonl line PER CONTENT BLOCK of an API message,\n // repeating message.id and usage on each — merge blocks into one canonical\n // assistant turn and count usage once per API message id.\n let lastAssistantApiId: string | null = null\n let lastAssistantIndex = -1\n\n for (const entry of entries) {\n if (entry.isSidechain !== wantSidechain) continue\n const message = entry.message\n if (entry.timestamp !== null) {\n if (startedAt === null) startedAt = entry.timestamp\n endedAt = entry.timestamp\n }\n\n if (entry.type === 'user') {\n lastAssistantApiId = null\n lastAssistantIndex = -1\n const content = message.content\n if (typeof content === 'string') {\n messages.push({ role: 'user', content })\n continue\n }\n if (!Array.isArray(content)) continue\n // A user line may interleave tool_result blocks (answers to the prior\n // assistant tool_use) with plain text; preserve order.\n let userText = ''\n for (const block of content) {\n if (!isRecord(block)) continue\n if (block.type === 'tool_result' && typeof block.tool_use_id === 'string') {\n messages.push({\n role: 'tool',\n tool_call_id: block.tool_use_id,\n content:\n blockText(block.content) || (typeof block.content === 'string' ? block.content : ''),\n })\n } else if (block.type === 'text' && typeof block.text === 'string') {\n userText += (userText.length > 0 ? '\\n' : '') + block.text\n }\n }\n if (userText.length > 0) messages.push({ role: 'user', content: userText })\n continue\n }\n\n // assistant\n if (typeof message.model === 'string') model = message.model\n const apiId = typeof message.id === 'string' ? message.id : null\n const continuesTurn = apiId !== null && apiId === lastAssistantApiId && lastAssistantIndex >= 0\n const msgUsage = message.usage\n if (isRecord(msgUsage) && !continuesTurn) {\n usage.tokensIn += typeof msgUsage.input_tokens === 'number' ? msgUsage.input_tokens : 0\n usage.tokensOut += typeof msgUsage.output_tokens === 'number' ? msgUsage.output_tokens : 0\n usage.cacheRead +=\n typeof msgUsage.cache_read_input_tokens === 'number' ? msgUsage.cache_read_input_tokens : 0\n usage.cacheWrite +=\n typeof msgUsage.cache_creation_input_tokens === 'number'\n ? msgUsage.cache_creation_input_tokens\n : 0\n }\n const content = message.content\n if (!Array.isArray(content)) continue\n let reasoning = ''\n let text = ''\n const toolCalls: ChatToolCall[] = []\n for (const block of content) {\n if (!isRecord(block)) continue\n if (\n block.type === 'thinking' &&\n typeof block.thinking === 'string' &&\n block.thinking.length > 0\n ) {\n reasoning += (reasoning.length > 0 ? '\\n' : '') + block.thinking\n } else if (block.type === 'text' && typeof block.text === 'string') {\n text += (text.length > 0 ? '\\n' : '') + block.text\n } else if (block.type === 'tool_use' && typeof block.id === 'string') {\n toolCalls.push({\n id: block.id,\n type: 'function',\n function: {\n name: typeof block.name === 'string' ? block.name : 'unknown',\n arguments: JSON.stringify(block.input ?? {}),\n },\n })\n }\n }\n if (reasoning.length === 0 && text.length === 0 && toolCalls.length === 0) continue\n if (continuesTurn) {\n const prev = messages[lastAssistantIndex]!\n if (text.length > 0) prev.content = prev.content === null ? text : `${prev.content}\\n${text}`\n if (reasoning.length > 0) {\n prev.reasoning_content =\n prev.reasoning_content === undefined\n ? reasoning\n : `${prev.reasoning_content}\\n${reasoning}`\n }\n if (toolCalls.length > 0) prev.tool_calls = [...(prev.tool_calls ?? []), ...toolCalls]\n continue\n }\n messages.push({\n role: 'assistant',\n content: text.length > 0 ? text : null,\n ...(reasoning.length > 0 ? { reasoning_content: reasoning } : {}),\n ...(toolCalls.length > 0 ? { tool_calls: toolCalls } : {}),\n })\n lastAssistantApiId = apiId\n lastAssistantIndex = messages.length - 1\n }\n\n return { messages, usage, startedAt, endedAt, model }\n}\n","/**\n * Read-only backfill reader over the opencode sqlite store\n * (~/.local/share/opencode/opencode.db) → canonical chat-with-tools messages.\n *\n * Schema consumed (observed, 2026-07): `session` rows carry directory /\n * parent_id / agent / model / cost / tokens_*; `message` rows carry a JSON\n * `data` blob ({role, modelID, providerID, tokens, cost, finish}); `part`\n * rows carry the actual content ({type: text|reasoning|tool|step-start|\n * step-finish|snapshot…}). Tool parts hold {callID, state:{input, output,\n * status}} — both the call and its result, which we split into an assistant\n * tool_call plus a role:\"tool\" result message.\n *\n * The store is mutable and can be corrupt (a `.corrupt-bak` sibling ships\n * next to it in the wild), so `openOpencodeDb` returns null instead of\n * throwing — callers record a gap line, never crash the backfill.\n */\n\nimport { homedir } from 'node:os'\nimport { join } from 'node:path'\nimport type { DatabaseSync } from 'node:sqlite'\nimport type { ChatMessage, ChatToolCall } from '../schema'\n\nexport const DEFAULT_OPENCODE_DB = join(homedir(), '.local', 'share', 'opencode', 'opencode.db')\n\nexport interface OpencodeSessionRow {\n id: string\n parentId: string | null\n directory: string\n agent: string | null\n /** Raw session.model JSON: {id, providerID, variant} where present. */\n model: { id?: string; providerID?: string } | null\n costUsd: number\n tokensInput: number\n tokensOutput: number\n tokensReasoning: number\n tokensCacheRead: number\n tokensCacheWrite: number\n timeCreated: number\n timeUpdated: number\n}\n\n// Opaque specifier: esbuild (bundling) and Vite (tests) both rewrite an\n// analyzable dynamic import and strip the `node:` prefix under an es20xx\n// target, which turns this builtin into a bogus \"sqlite\" package lookup.\n// Composing the string at runtime defeats that analysis in both.\nconst NODE_SQLITE_SPECIFIER = ['node', 'sqlite'].join(':')\n\n/** Open the store read-only; null = unavailable/corrupt (caller records a gap). */\nexport async function openOpencodeDb(\n path: string = DEFAULT_OPENCODE_DB,\n): Promise<DatabaseSync | null> {\n try {\n const { DatabaseSync } = (await import(\n /* @vite-ignore */ NODE_SQLITE_SPECIFIER\n )) as typeof import('node:sqlite')\n const db = new DatabaseSync(path, { readOnly: true })\n // Probe: a corrupt store can open() fine and fail on first page read.\n db.prepare('SELECT id FROM session LIMIT 1').get()\n return db\n } catch {\n return null\n }\n}\n\nconst isRecord = (v: unknown): v is Record<string, unknown> =>\n typeof v === 'object' && v !== null && !Array.isArray(v)\n\nfunction parseSessionRow(row: Record<string, unknown>): OpencodeSessionRow {\n let model: OpencodeSessionRow['model'] = null\n if (typeof row.model === 'string' && row.model.length > 0) {\n try {\n const parsed: unknown = JSON.parse(row.model)\n if (isRecord(parsed)) model = parsed as { id?: string; providerID?: string }\n } catch {\n model = null\n }\n }\n return {\n id: String(row.id),\n parentId: row.parent_id === null || row.parent_id === undefined ? null : String(row.parent_id),\n directory: String(row.directory),\n agent: row.agent === null || row.agent === undefined ? null : String(row.agent),\n model,\n costUsd: Number(row.cost ?? 0),\n tokensInput: Number(row.tokens_input ?? 0),\n tokensOutput: Number(row.tokens_output ?? 0),\n tokensReasoning: Number(row.tokens_reasoning ?? 0),\n tokensCacheRead: Number(row.tokens_cache_read ?? 0),\n tokensCacheWrite: Number(row.tokens_cache_write ?? 0),\n timeCreated: Number(row.time_created ?? 0),\n timeUpdated: Number(row.time_updated ?? 0),\n }\n}\n\nconst SESSION_COLUMNS =\n 'id, parent_id, directory, agent, model, cost, tokens_input, tokens_output, tokens_reasoning, tokens_cache_read, tokens_cache_write, time_created, time_updated'\n\n/** Sessions whose cwd is `directory` (the worker-clone join key). */\nexport function findOpencodeSessionsByDirectory(\n db: DatabaseSync,\n directory: string,\n): OpencodeSessionRow[] {\n const rows = db\n .prepare(`SELECT ${SESSION_COLUMNS} FROM session WHERE directory = ? ORDER BY time_created`)\n .all(directory) as Array<Record<string, unknown>>\n return rows.map(parseSessionRow)\n}\n\nexport function findOpencodeSessionById(\n db: DatabaseSync,\n sessionId: string,\n): OpencodeSessionRow | null {\n const row = db.prepare(`SELECT ${SESSION_COLUMNS} FROM session WHERE id = ?`).get(sessionId) as\n | Record<string, unknown>\n | undefined\n return row === undefined ? null : parseSessionRow(row)\n}\n\ninterface OpencodePart {\n type?: string\n text?: string\n tool?: string\n callID?: string\n state?: { status?: string; input?: unknown; output?: unknown }\n}\n\nfunction toolResultContent(output: unknown): string {\n if (typeof output === 'string') return output\n if (output === null || output === undefined) return ''\n return JSON.stringify(output)\n}\n\n/**\n * Convert one session's message+part rows into canonical messages.\n * An opencode assistant message row spans several model steps; each step's\n * parts (reasoning → text → tool …) become one assistant message followed by\n * the role:\"tool\" results of its calls, preserving order.\n */\nexport function readOpencodeSessionMessages(db: DatabaseSync, sessionId: string): ChatMessage[] {\n const messageRows = db\n .prepare('SELECT id, data FROM message WHERE session_id = ? ORDER BY time_created, id')\n .all(sessionId) as Array<{ id: string; data: string }>\n const partsStmt = db.prepare('SELECT data FROM part WHERE message_id = ? ORDER BY id')\n\n const messages: ChatMessage[] = []\n for (const messageRow of messageRows) {\n let data: Record<string, unknown>\n try {\n const parsed: unknown = JSON.parse(messageRow.data)\n if (!isRecord(parsed)) continue\n data = parsed\n } catch {\n continue\n }\n const parts: OpencodePart[] = []\n for (const row of partsStmt.all(messageRow.id) as Array<{ data: string }>) {\n try {\n const parsed: unknown = JSON.parse(row.data)\n if (isRecord(parsed)) parts.push(parsed as OpencodePart)\n } catch {\n // Malformed part payload: skip the part, keep the message.\n }\n }\n\n if (data.role === 'user') {\n const text = parts\n .filter((p) => p.type === 'text' && typeof p.text === 'string')\n .map((p) => p.text as string)\n .join('\\n')\n messages.push({ role: 'user', content: text })\n continue\n }\n if (data.role !== 'assistant') continue\n\n // Split the row into steps at step-start boundaries; parts before the\n // first step-start (none observed, but tolerated) form an implicit step.\n const steps: OpencodePart[][] = []\n let current: OpencodePart[] = []\n for (const part of parts) {\n if (part.type === 'step-start') {\n if (current.length > 0) steps.push(current)\n current = []\n continue\n }\n if (part.type === 'step-finish' || part.type === 'snapshot' || part.type === 'patch') continue\n current.push(part)\n }\n if (current.length > 0) steps.push(current)\n\n for (const step of steps) {\n const reasoning = step\n .filter((p) => p.type === 'reasoning' && typeof p.text === 'string' && p.text.length > 0)\n .map((p) => p.text as string)\n .join('\\n')\n const text = step\n .filter((p) => p.type === 'text' && typeof p.text === 'string')\n .map((p) => p.text as string)\n .join('\\n')\n const toolParts = step.filter((p) => p.type === 'tool' && typeof p.callID === 'string')\n const toolCalls: ChatToolCall[] = toolParts.map((p) => ({\n id: p.callID as string,\n type: 'function',\n function: {\n name: p.tool ?? 'unknown',\n arguments: JSON.stringify(p.state?.input ?? {}),\n },\n }))\n if (reasoning.length === 0 && text.length === 0 && toolCalls.length === 0) continue\n messages.push({\n role: 'assistant',\n content: text.length > 0 ? text : null,\n ...(reasoning.length > 0 ? { reasoning_content: reasoning } : {}),\n ...(toolCalls.length > 0 ? { tool_calls: toolCalls } : {}),\n })\n for (const p of toolParts) {\n messages.push({\n role: 'tool',\n tool_call_id: p.callID as string,\n name: p.tool ?? 'unknown',\n content: toolResultContent(p.state?.output),\n })\n }\n }\n }\n return messages\n}\n"],"mappings":"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","names":["content","homedir","join","isRecord"]}
|
|
@@ -3,13 +3,13 @@ import {
|
|
|
3
3
|
aggregateRunScore,
|
|
4
4
|
clamp01,
|
|
5
5
|
computeFindingId
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-DT7OXY3C.js";
|
|
7
7
|
import {
|
|
8
8
|
callLlmJson,
|
|
9
9
|
costReceiptFromLlm,
|
|
10
10
|
costReceiptFromLlmError,
|
|
11
11
|
maximumChargeForLlmRequest
|
|
12
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-EQUK3RFS.js";
|
|
13
13
|
import {
|
|
14
14
|
CostLedger
|
|
15
15
|
} from "./chunk-GRCDRKII.js";
|
|
@@ -747,4 +747,4 @@ export {
|
|
|
747
747
|
runSemanticConceptJudge,
|
|
748
748
|
createSemanticConceptJudge
|
|
749
749
|
};
|
|
750
|
-
//# sourceMappingURL=chunk-
|
|
750
|
+
//# sourceMappingURL=chunk-W5B3ZGP3.js.map
|
package/dist/cli.js
CHANGED
|
@@ -5,8 +5,12 @@ import {
|
|
|
5
5
|
runRpcBatch,
|
|
6
6
|
runRpcOnce,
|
|
7
7
|
startServerAsync
|
|
8
|
-
} from "./chunk-
|
|
9
|
-
import
|
|
8
|
+
} from "./chunk-HM6V7F3M.js";
|
|
9
|
+
import {
|
|
10
|
+
runRolloutReleaseCli
|
|
11
|
+
} from "./chunk-IPYXE555.js";
|
|
12
|
+
import "./chunk-MAX3TN3C.js";
|
|
13
|
+
import "./chunk-EQUK3RFS.js";
|
|
10
14
|
import "./chunk-GRCDRKII.js";
|
|
11
15
|
import "./chunk-VI2UW6B6.js";
|
|
12
16
|
import "./chunk-PC4UYEBM.js";
|
|
@@ -79,6 +83,9 @@ Commands:
|
|
|
79
83
|
Like 'rpc' but JSONL in / JSONL out.
|
|
80
84
|
openapi [--out openapi.json]
|
|
81
85
|
Write the OpenAPI 3.1 spec.
|
|
86
|
+
rollout-release <ledger.jsonl...> --out <dir> [--formats sft,verifiers,rft,raw] [--include-proposers] [--push <org/name>]
|
|
87
|
+
Build a HuggingFace-ready dataset dir from tangle.rollout.v1 ledgers:
|
|
88
|
+
validate, fail-closed split filter, scrub, export formats + card.
|
|
82
89
|
version
|
|
83
90
|
Print server + wire-protocol version JSON.
|
|
84
91
|
|
|
@@ -144,6 +151,9 @@ async function main() {
|
|
|
144
151
|
console.log(`[agent-eval] wrote OpenAPI 3.1 spec to ${out}`);
|
|
145
152
|
return 0;
|
|
146
153
|
}
|
|
154
|
+
case "rollout-release": {
|
|
155
|
+
return await runRolloutReleaseCli(process.argv.slice(3));
|
|
156
|
+
}
|
|
147
157
|
case "version": {
|
|
148
158
|
process.stdout.write(`${JSON.stringify(handleVersion(), null, 2)}
|
|
149
159
|
`);
|
|
@@ -181,6 +191,7 @@ var FLAGS_BY_COMMAND = {
|
|
|
181
191
|
"": /* @__PURE__ */ new Set()
|
|
182
192
|
};
|
|
183
193
|
function assertKnownFlags(command, flags) {
|
|
194
|
+
if (command === "rollout-release") return;
|
|
184
195
|
const allowed = FLAGS_BY_COMMAND[command];
|
|
185
196
|
if (!allowed) return;
|
|
186
197
|
const unknown = Object.keys(flags).filter((flag) => !allowed.has(flag));
|