@agent-finops/core 0.8.1 → 0.9.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.
Files changed (44) hide show
  1. package/README.md +5 -3
  2. package/dist/actionPlanner.d.ts +140 -0
  3. package/dist/actionPlanner.js +938 -0
  4. package/dist/actionVerification.d.ts +1240 -0
  5. package/dist/actionVerification.js +1028 -0
  6. package/dist/activitySnapshot.d.ts +142 -50
  7. package/dist/activitySnapshot.js +145 -6
  8. package/dist/activitySnapshotCache.d.ts +8 -1
  9. package/dist/activitySnapshotCache.js +103 -7
  10. package/dist/agentEconomicsReceipt.d.ts +74 -74
  11. package/dist/glance.d.ts +27 -1
  12. package/dist/glance.js +151 -12
  13. package/dist/index.d.ts +11 -2
  14. package/dist/index.js +10 -1
  15. package/dist/localAgentFormats/gemini.js +2 -2
  16. package/dist/localAgentFormats/registry.js +6 -2
  17. package/dist/localAgentFormats/runtimeRegistry.js +5 -2
  18. package/dist/localAgentFormats/types.d.ts +2 -1
  19. package/dist/localAgentLogs.d.ts +362 -3
  20. package/dist/localAgentLogs.js +1964 -165
  21. package/dist/modelPricing.d.ts +1 -1
  22. package/dist/modelPricing.js +1 -1
  23. package/dist/projectEconomics.d.ts +617 -0
  24. package/dist/projectEconomics.js +620 -0
  25. package/dist/projectEconomicsBuilder.d.ts +89 -0
  26. package/dist/projectEconomicsBuilder.js +473 -0
  27. package/dist/projectIndexStore.d.ts +545 -0
  28. package/dist/projectIndexStore.js +606 -0
  29. package/dist/providerConnectors.d.ts +59 -1
  30. package/dist/providerConnectors.js +175 -11
  31. package/dist/qualitativeIndexCache.d.ts +494 -0
  32. package/dist/qualitativeIndexCache.js +930 -0
  33. package/dist/resultCard.d.ts +350 -0
  34. package/dist/resultCard.js +604 -0
  35. package/dist/runtimeCommands.d.ts +21 -0
  36. package/dist/runtimeCommands.js +27 -0
  37. package/dist/scanGuard.d.ts +3 -1
  38. package/dist/scanGuard.js +164 -4
  39. package/dist/schema.d.ts +31 -31
  40. package/dist/sessionVitals.d.ts +145 -0
  41. package/dist/sessionVitals.js +521 -0
  42. package/dist/toolInvocations.d.ts +40 -1
  43. package/dist/toolInvocations.js +101 -20
  44. package/package.json +1 -1
package/dist/glance.js CHANGED
@@ -3,6 +3,7 @@ import { canPriceTokenUsageAtScope, estimateTokenCostUsd, PRICING_TABLE_AS_OF }
3
3
  import { subscriptionPlans } from "./planMath.js";
4
4
  import { buildContextHealth } from "./contextHealth.js";
5
5
  import { localAgentFormatDescriptors, localAgentFormatSupports } from "./localAgentFormats/registry.js";
6
+ import { aibillImproveCommandV0 } from "./runtimeCommands.js";
6
7
  const HOUR_MS = 60 * 60 * 1_000;
7
8
  const DAY_MS = 24 * HOUR_MS;
8
9
  /**
@@ -55,17 +56,37 @@ export function buildUsageGlance(calls, options = {}) {
55
56
  const focusSessions = latest?.project && !isGenericProject(latest.project)
56
57
  ? windowSessions.filter((session) => session.project === latest.project)
57
58
  : windowSessions;
58
- const focus = buildMainFocus(focusSessions, focusWindowDays, now);
59
- const sessionHealth = suppliedContextHealth ?? buildContextHealth({ calls: safeCalls, now });
60
- const anomaly = anomalyFromContextHealth(sessionHealth);
61
- const primaryAction = buildPrimaryAction({
62
- currentSession,
63
- focus,
64
- limits,
65
- sessionHealth,
66
- generatedAt: now.toISOString(),
67
- filesParsed: options.filesParsed ?? 0
68
- });
59
+ const qualitativeCoverage = options.qualitativeCoverage ?? {
60
+ status: "complete",
61
+ selectedFiles: options.filesParsed ?? 0,
62
+ readCompletely: options.filesParsed ?? 0,
63
+ skippedForBudget: 0
64
+ };
65
+ const qualitativeComplete = qualitativeCoverage.status === "complete";
66
+ const focus = qualitativeComplete
67
+ ? buildMainFocus(focusSessions, focusWindowDays, now)
68
+ : null;
69
+ const baseSessionHealth = suppliedContextHealth ?? buildContextHealth({ calls: safeCalls, now });
70
+ const sessionHealth = {
71
+ ...baseSessionHealth,
72
+ qualitativeCoverage
73
+ };
74
+ const anomaly = qualitativeComplete ? anomalyFromContextHealth(sessionHealth) : null;
75
+ const primaryAction = qualitativeComplete
76
+ ? buildPrimaryAction({
77
+ currentSession,
78
+ focus,
79
+ limits,
80
+ sessionHealth,
81
+ generatedAt: now.toISOString(),
82
+ filesParsed: options.filesParsed ?? 0
83
+ })
84
+ : buildCoverageLimitedPrimaryAction({
85
+ currentSession,
86
+ sessionHealth,
87
+ coverage: qualitativeCoverage
88
+ });
89
+ const tokenExperiment = sanitizeActionVerificationProjection(options.actionVerificationProjection);
69
90
  const detectedAgents = (options.detectedAgents ?? uniqueAgents(safeCalls))
70
91
  .filter((agent) => localAgentFormatSupports(agent, "glance"));
71
92
  const agentsWithCurrentLimits = new Set(limits.filter((limit) => limit.freshness === "current").map((limit) => limit.agent));
@@ -81,14 +102,19 @@ export function buildUsageGlance(calls, options = {}) {
81
102
  "A five-hour percentage is current for at most five hours after observation; a weekly percentage is current for at most 24 hours. Older transcript evidence is labeled stale and never drives an action.",
82
103
  "Main focus is a local summary of observed human prompts and tool activity, not elapsed time or spend; raw prompts are not returned.",
83
104
  "The primary action combines Context Health, Main focus, and reported runway locally. It only provides a copyable handoff prompt and never runs an agent automatically.",
105
+ "A token-test percentage compares matched local session cohorts guarded by explicit quality evidence; it is not certified savings, verified outcome ROI, or a provider bill.",
84
106
  "Claude Code transcripts do not report plan headroom. Missing limits remain unavailable instead of being inferred.",
85
- "Cursor and GitHub Copilot require their provider connections because their local chat stores are not treated as authoritative billing transcripts."
107
+ "Cursor and GitHub Copilot require their provider connections because their local chat stores are not treated as authoritative billing transcripts.",
108
+ ...(qualitativeComplete ? [] : [
109
+ "Main focus, anomaly, and context-change handoff are unavailable because the bounded qualitative index is incomplete; no global driver was inferred from a selected subset."
110
+ ])
86
111
  ];
87
112
  return {
88
113
  dataMode: "local_transcripts",
89
114
  generatedAt: now.toISOString(),
90
115
  coverage: {
91
116
  filesParsed: options.filesParsed ?? 0,
117
+ qualitative: qualitativeCoverage,
92
118
  supportedTranscriptAgents: supportedFormats.map((descriptor) => descriptor.id),
93
119
  detectedAgents,
94
120
  rateLimitMetadata: supportedFormats.map((descriptor) => ({
@@ -146,6 +172,14 @@ export function buildUsageGlance(calls, options = {}) {
146
172
  execution: "copy_prompt",
147
173
  automaticExecution: false
148
174
  },
175
+ tokenExperiment: {
176
+ source: tokenExperiment
177
+ ? "canonical_action_verification_projection"
178
+ : "not_available",
179
+ calculation: "core_experiment_evaluator",
180
+ cohort: "matched_local_sessions",
181
+ automaticExecution: false
182
+ },
149
183
  network: {
150
184
  uploaded: false
151
185
  }
@@ -157,9 +191,114 @@ export function buildUsageGlance(calls, options = {}) {
157
191
  anomaly,
158
192
  sessionHealth,
159
193
  primaryAction,
194
+ ...(tokenExperiment ? { tokenExperiment } : {}),
160
195
  caveats
161
196
  };
162
197
  }
198
+ function buildCoverageLimitedPrimaryAction(input) {
199
+ const project = safeActionMetadata(input.currentSession?.project, 80);
200
+ const status = input.coverage.status === "partial" ? "partial" : "not available";
201
+ return {
202
+ kind: "session_handoff",
203
+ intent: "inspect_current_work",
204
+ label: project ? `Refresh evidence · ${project}` : "Refresh evidence",
205
+ detail: `Main focus unavailable · qualitative index ${status}`,
206
+ ...(project ? { project } : {}),
207
+ agentPrompt: [
208
+ "aibill's bounded qualitative evidence is incomplete.",
209
+ "Do not infer a global main focus, waste cause, or context change from the selected subset.",
210
+ `Run \`${aibillImproveCommandV0()}\` from the exact project root to refresh the private index, then review the new evidence before editing.`
211
+ ].join("\n"),
212
+ source: "context_health_focus_and_reported_runway",
213
+ confidence: "low",
214
+ execution: "copy_prompt",
215
+ requiresUserConfirmation: true,
216
+ evidenceWindowDays: input.sessionHealth.deadContext.windowDays
217
+ };
218
+ }
219
+ const actionVerificationStates = new Set([
220
+ "collect_baseline",
221
+ "approve_one_change",
222
+ "collect_post_change",
223
+ "review_measured_result",
224
+ "rollback",
225
+ "resolve_evidence",
226
+ "rolled_back",
227
+ "cancelled"
228
+ ]);
229
+ const actionVerificationTones = new Set([
230
+ "neutral",
231
+ "attention",
232
+ "positive",
233
+ "negative"
234
+ ]);
235
+ const actionVerificationEvidenceLabels = new Set([
236
+ "calculated",
237
+ "missing"
238
+ ]);
239
+ const actionVerificationQualityLabels = new Set([
240
+ "held",
241
+ "regressed",
242
+ "insufficient"
243
+ ]);
244
+ const actionVerificationQualityEvidence = new Set([
245
+ "verified",
246
+ "observed",
247
+ "user_declared",
248
+ "missing"
249
+ ]);
250
+ const experimentIdPattern = /^tre_v0_[a-f0-9]{64}$/;
251
+ const findingIdPattern = /^wf_v0_[a-f0-9]{64}$/;
252
+ const candidateKeyPattern = /^wfc_v0_[a-f0-9]{64}$/;
253
+ /**
254
+ * Treat the optional adapter input as untrusted at runtime. A malformed or
255
+ * internally inconsistent projection is omitted instead of becoming a stale
256
+ * or invented Glance claim. This function deliberately does not derive any
257
+ * experiment result.
258
+ */
259
+ function sanitizeActionVerificationProjection(input) {
260
+ if (!input || typeof input !== "object")
261
+ return undefined;
262
+ const safe = sanitizeStringMetadata(input);
263
+ if (safe.schemaVersion !== 0 ||
264
+ !experimentIdPattern.test(safe.experimentId) ||
265
+ !findingIdPattern.test(safe.findingId) ||
266
+ !candidateKeyPattern.test(safe.candidateKey) ||
267
+ !actionVerificationStates.has(safe.state) ||
268
+ !actionVerificationTones.has(safe.tone) ||
269
+ !actionVerificationEvidenceLabels.has(safe.evidenceLabel) ||
270
+ !actionVerificationQualityLabels.has(safe.qualityLabel) ||
271
+ !actionVerificationQualityEvidence.has(safe.qualityEvidence) ||
272
+ !isSafeExperimentCount(safe.baselineSessions) ||
273
+ !isSafeExperimentCount(safe.postChangeSessions) ||
274
+ !isSafeExperimentCount(safe.minimumSessions) ||
275
+ safe.minimumSessions < 1 ||
276
+ (safe.reductionPercent !== null && (!Number.isFinite(safe.reductionPercent) ||
277
+ safe.reductionPercent > 100 ||
278
+ safe.reductionPercent < -1_000_000))) {
279
+ return undefined;
280
+ }
281
+ const measured = safe.state === "review_measured_result";
282
+ const claimQualityEvidence = safe.qualityEvidence === "verified" ||
283
+ safe.qualityEvidence === "observed" ||
284
+ safe.qualityEvidence === "user_declared";
285
+ if (safe.reductionPercent !== null) {
286
+ const claimEvidenceIsComplete = safe.evidenceLabel === "calculated" &&
287
+ safe.qualityLabel === "held" &&
288
+ claimQualityEvidence;
289
+ const signMatchesState = (measured && safe.reductionPercent >= 0) ||
290
+ (safe.state === "rollback" && safe.reductionPercent < 0);
291
+ if (!claimEvidenceIsComplete || !signMatchesState)
292
+ return undefined;
293
+ }
294
+ else if (measured) {
295
+ return undefined;
296
+ }
297
+ return safe;
298
+ }
299
+ function isSafeExperimentCount(value) {
300
+ return Number.isSafeInteger(value) && value >= 0;
301
+ }
163
302
  function toGlancePlan(agent, detectedPlans) {
164
303
  const detected = detectedPlans.find((plan) => plan.agent === agent);
165
304
  if (!detected)
package/dist/index.d.ts CHANGED
@@ -1,4 +1,6 @@
1
1
  export * from "./analyze.js";
2
+ export * from "./actionPlanner.js";
3
+ export * from "./actionVerification.js";
2
4
  export * from "./agentInventory.js";
3
5
  export * from "./agentEconomicsReceipt.js";
4
6
  export * from "./activitySnapshot.js";
@@ -12,16 +14,23 @@ export * from "./discovery.js";
12
14
  export * from "./glance.js";
13
15
  export * from "./toolInvocations.js";
14
16
  export * from "./insights.js";
15
- export { aggregateCalls, dedupeCumulativeSessionCalls, latestObservedWorkingDirectory, loadLocalAgentFinancialUsage, loadLocalAgentUsage, parseClaudeCodeTranscript, parseCodexRollout, sanitizeLocalActivityText } from "./localAgentLogs.js";
16
- export type { LocalAgentActivity, LocalAgentCall, LocalAgentFinancialLogOptions, LocalAgentLogDiagnostic, LocalAgentLogDiagnosticCode, LocalAgentLogOptions, LocalAgentLogResult, LocalAgentRateLimitSnapshot, LocalAgentRateLimitWindow, LocalAgentSourceScan, LocalAgentTurnUsage } from "./localAgentLogs.js";
17
+ export { aggregateCalls, codexHeaderProbesPerScan, dedupeCumulativeSessionCalls, defaultStreamedBytesPerRun, hasCompleteQualitativeCoverage, hasExactSelectedQualitativeEvidence, latestObservedWorkingDirectory, loadLocalAgentActionEvidence, loadLocalAgentFinancialUsage, loadLocalAgentUsage, localAgentQualitativeParserVersion, parseClaudeCodeTranscript, parseCodexRollout, SAFE_QUALITATIVE_SCAN_POLICY, sanitizeLocalActivityText } from "./localAgentLogs.js";
18
+ export type { LocalAgentActivity, LocalAgentCall, LocalAgentCompletionEvidence, LocalAgentFinancialLogOptions, LocalAgentOwnershipIndexAdapter, LocalAgentOwnershipRecord, LocalAgentQualitativeIndexAdapter, LocalAgentQualitativeIndexKey, LocalAgentQualitativeIndexValue, LocalAgentQualitativeScanPolicy, LocalAgentStreamCheckpointAdapter, LocalAgentStreamCheckpointRecord, LocalAgentLogDiagnostic, LocalAgentLogDiagnosticCode, LocalAgentLogOptions, LocalAgentLogResult, LocalAgentRateLimitSnapshot, LocalAgentRateLimitWindow, LocalAgentSourceScan, LocalAgentTokenComponentEvidence, LocalAgentTurnUsage } from "./localAgentLogs.js";
17
19
  export * from "./localAgentFormats/registry.js";
18
20
  export type * from "./localAgentFormats/types.js";
19
21
  export * from "./modelPricing.js";
20
22
  export * from "./planDetection.js";
21
23
  export * from "./planMath.js";
24
+ export * from "./projectEconomics.js";
25
+ export * from "./resultCard.js";
26
+ export * from "./projectEconomicsBuilder.js";
27
+ export * from "./qualitativeIndexCache.js";
28
+ export * from "./projectIndexStore.js";
29
+ export * from "./runtimeCommands.js";
22
30
  export * from "./sampleData.js";
23
31
  export * from "./scanGuard.js";
24
32
  export * from "./schema.js";
33
+ export * from "./sessionVitals.js";
25
34
  export * from "./sourceRegistry.js";
26
35
  export * from "./sourceStatus.js";
27
36
  export * from "./stateTrust.js";
package/dist/index.js CHANGED
@@ -1,4 +1,6 @@
1
1
  export * from "./analyze.js";
2
+ export * from "./actionPlanner.js";
3
+ export * from "./actionVerification.js";
2
4
  export * from "./agentInventory.js";
3
5
  export * from "./agentEconomicsReceipt.js";
4
6
  export * from "./activitySnapshot.js";
@@ -12,14 +14,21 @@ export * from "./discovery.js";
12
14
  export * from "./glance.js";
13
15
  export * from "./toolInvocations.js";
14
16
  export * from "./insights.js";
15
- export { aggregateCalls, dedupeCumulativeSessionCalls, latestObservedWorkingDirectory, loadLocalAgentFinancialUsage, loadLocalAgentUsage, parseClaudeCodeTranscript, parseCodexRollout, sanitizeLocalActivityText } from "./localAgentLogs.js";
17
+ export { aggregateCalls, codexHeaderProbesPerScan, dedupeCumulativeSessionCalls, defaultStreamedBytesPerRun, hasCompleteQualitativeCoverage, hasExactSelectedQualitativeEvidence, latestObservedWorkingDirectory, loadLocalAgentActionEvidence, loadLocalAgentFinancialUsage, loadLocalAgentUsage, localAgentQualitativeParserVersion, parseClaudeCodeTranscript, parseCodexRollout, SAFE_QUALITATIVE_SCAN_POLICY, sanitizeLocalActivityText } from "./localAgentLogs.js";
16
18
  export * from "./localAgentFormats/registry.js";
17
19
  export * from "./modelPricing.js";
18
20
  export * from "./planDetection.js";
19
21
  export * from "./planMath.js";
22
+ export * from "./projectEconomics.js";
23
+ export * from "./resultCard.js";
24
+ export * from "./projectEconomicsBuilder.js";
25
+ export * from "./qualitativeIndexCache.js";
26
+ export * from "./projectIndexStore.js";
27
+ export * from "./runtimeCommands.js";
20
28
  export * from "./sampleData.js";
21
29
  export * from "./scanGuard.js";
22
30
  export * from "./schema.js";
31
+ export * from "./sessionVitals.js";
23
32
  export * from "./sourceRegistry.js";
24
33
  export * from "./sourceStatus.js";
25
34
  export * from "./stateTrust.js";
@@ -176,8 +176,8 @@ function processMessage(message, state) {
176
176
  state.diagnostics.add("missing_timestamp");
177
177
  return;
178
178
  }
179
- if (state.sinceMs !== undefined && Date.parse(timestamp) < state.sinceMs)
180
- return;
179
+ // Window-blind on purpose (financial cache correctness): the loader's
180
+ // final timestamp filter performs all narrowing.
181
181
  const parsedTokens = parseTokens(message.tokens);
182
182
  const metadata = mergeMetadata(state.metadata, message);
183
183
  const attribution = explicitAttribution(message, metadata) ??
@@ -46,6 +46,7 @@ const descriptors = [
46
46
  fieldsRead: [
47
47
  "timestamp, model, and token-usage components",
48
48
  "session and working-directory metadata for local deduplication and attribution",
49
+ "explicit system/turn_duration work-unit completion markers and transcript version strings when present",
49
50
  "human-prompt and tool metadata for privacy-reduced local activity summaries"
50
51
  ],
51
52
  verified: [
@@ -65,7 +66,8 @@ const descriptors = [
65
66
  ],
66
67
  limitations: [
67
68
  "Malformed lines are skipped and reported.",
68
- "Incomplete token shapes remain unpriced with missing financial evidence instead of becoming $0."
69
+ "Incomplete token shapes remain unpriced with missing financial evidence instead of becoming $0.",
70
+ "A system/turn_duration marker proves only that the latest observed turn completed; it does not prove permanent transcript closure, and missing or inconsistent completion evidence stays ineligible for automatic before/after cohorts."
69
71
  ]
70
72
  },
71
73
  fixtures: ["claude-code-v1"]
@@ -115,6 +117,7 @@ const descriptors = [
115
117
  ],
116
118
  fieldsRead: [
117
119
  "session metadata, timestamps, model, and cumulative/last-turn token usage",
120
+ "explicit event_msg/task_complete work-unit completion markers and session_meta cli_version strings when present",
118
121
  "transcript-reported rate-limit windows when present",
119
122
  "tool-call metadata for local attribution and optional privacy-safe invocation counts"
120
123
  ],
@@ -135,7 +138,8 @@ const descriptors = [
135
138
  ],
136
139
  limitations: [
137
140
  "Only rollout-*.jsonl files are parsed as Codex sessions.",
138
- "Incomplete, regressing, or total-only token shapes remain unpriced with missing financial evidence."
141
+ "Incomplete, regressing, or total-only token shapes remain unpriced with missing financial evidence.",
142
+ "An event_msg/task_complete marker proves only that the latest observed task completed; it does not prove permanent transcript closure, and missing or inconsistent completion evidence stays ineligible for automatic before/after cohorts."
139
143
  ]
140
144
  },
141
145
  fixtures: ["codex-v1"]
@@ -24,9 +24,12 @@ const runtimes = [
24
24
  const collector = collectInvocationEvidence
25
25
  ? createCodexInvocationCollector(sinceMs)
26
26
  : undefined;
27
+ const calls = parseCodexRollout(content, collector?.consume, onDiagnostic);
28
+ const invocationFile = collector?.finish();
27
29
  return {
28
- calls: parseCodexRollout(content, collector?.consume, onDiagnostic),
29
- ...(collector ? { invocationFile: collector.finish() } : {})
30
+ calls,
31
+ ...(invocationFile ? { invocationFile } : {}),
32
+ ...(collector ? { invocationWindowProof: collector.windowProof() } : {})
30
33
  };
31
34
  },
32
35
  parseFinancialFile: readCodexFinancialFileForRegistry
@@ -1,5 +1,5 @@
1
1
  import type { LocalAgentCall, LocalAgentLogDiagnostic, LocalAgentSourceScan } from "../localAgentLogs.js";
2
- import type { ParsedInvocationFile } from "../toolInvocations.js";
2
+ import type { ParsedInvocationFile, ParsedInvocationWindowProof } from "../toolInvocations.js";
3
3
  /**
4
4
  * Public format identity contract. Add future parser IDs here as part of the
5
5
  * registry-owned change so existing exhaustive consumers do not see an
@@ -78,6 +78,7 @@ export type LocalAgentFormatParseContext = {
78
78
  export type LocalAgentFormatParseResult = {
79
79
  calls: LocalAgentCall[];
80
80
  invocationFile?: ParsedInvocationFile;
81
+ invocationWindowProof?: ParsedInvocationWindowProof;
81
82
  };
82
83
  export type LocalAgentFormatFinancialFileContext = {
83
84
  filePath: string;