agentwrangler 0.1.1-next.bca85ea → 0.1.1-next.fbbbe7f
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 +162 -82
- package/dist/apply/jobs.js +3 -13
- package/dist/daemon/http.js +0 -2
- package/dist/daemon/index.js +6 -8
- package/dist/daemon/router.js +6 -63
- package/dist/detector/context-history-retention.js +0 -49
- package/dist/detector/detectors/d10_catalog_footprint.js +2 -2
- package/dist/detector/detectors/d1_ctx_always_loaded.js +9 -9
- package/dist/detector/detectors/d2_session_long_full_context.js +2 -2
- package/dist/detector/detectors/d4_model_mismatch.js +3 -3
- package/dist/detector/detectors/d6_tool_result_bloat.js +1 -1
- package/dist/detector/detectors/d7_loop_retry_waste.js +1 -1
- package/dist/detector/detectors/d8_cache_write_churn.js +2 -2
- package/dist/detector/detectors/d9_idle_background_session.js +1 -1
- package/dist/detector/measurement.js +3 -14
- package/dist/hook/precompact-checkpoint-hook.mjs +13 -26
- package/dist/ingest/pricing.js +0 -15
- package/dist/query/api/cost-per-success.js +1 -13
- package/dist/query/api/delivery.js +3 -25
- package/dist/query/api/effectiveness.js +5 -30
- package/dist/query/api/efficiency-headroom.js +14 -12
- package/dist/query/api/outcomes.js +1 -2
- package/dist/query/api/overview.js +6 -90
- package/dist/query/api/recommendations-ledger.js +14 -9
- package/dist/query/api/recommendations.js +1 -21
- package/dist/query/settings-store.js +0 -4
- package/dist/query/spend.js +2 -64
- package/dist/ui/assets/{BarChart-CIjCNfbI.js → BarChart-p1usctxJ.js} +3 -3
- package/dist/ui/assets/BriefsPage-CneqLVGC.js +2 -0
- package/dist/ui/assets/CacheWriteSpikesChart-zNRZbzy9.js +1 -0
- package/dist/ui/assets/Chip-Bu6udQGq.js +1 -0
- package/dist/ui/assets/ComposedChart-8D5E-Po_.js +1 -0
- package/dist/ui/assets/{EmptyState-B8vpug3_.js → EmptyState-CvQeJjjG.js} +1 -1
- package/dist/ui/assets/FlavorDecomposition-xcMLA50f.js +1 -0
- package/dist/ui/assets/FrictionCell-OafGLGxT.js +2 -0
- package/dist/ui/assets/GlossaryPage-BmOP3Syj.js +1 -0
- package/dist/ui/assets/HotSessionsPage-FoxuuDLF.js +1 -0
- package/dist/ui/assets/{InfoTip-CL8RC4vz.js → InfoTip-Dx7BHpBu.js} +1 -1
- package/dist/ui/assets/{Legend-CB7sqqJH.js → Legend--q6-rQGk.js} +3 -3
- package/dist/ui/assets/{Line-B9XY30uT.js → Line-DzVtUwF4.js} +1 -1
- package/dist/ui/assets/OverviewPage-BjSQYNvS.js +2 -0
- package/dist/ui/assets/RecommendationsPage-CRTpzcHs.js +2 -0
- package/dist/ui/assets/{Scatter-BSWm_yIZ.js → Scatter-Cr_hCkjG.js} +1 -1
- package/dist/ui/assets/SessionDetailPage-Dd3u8kVI.js +1 -0
- package/dist/ui/assets/SettingsPage-BPVQyjlc.js +25 -0
- package/dist/ui/assets/{Skeleton-Dp_m79B8.js → Skeleton-CcD61x7q.js} +1 -1
- package/dist/ui/assets/SpendPercentileChip-BaQhJQAn.js +1 -0
- package/dist/ui/assets/TrendChart-Baz3GGgE.js +1 -0
- package/dist/ui/assets/WorkspaceDetailPage-yk4z_EtQ.js +1 -0
- package/dist/ui/assets/WorkspacesPage-BDtclXJP.js +1 -0
- package/dist/ui/assets/{CartesianChart-DEnDOGKj.js → chart-theme-JH5aQymO.js} +36 -36
- package/dist/ui/assets/graphicalItemSelectors-CCbl8AKC.js +1 -0
- package/dist/ui/assets/{index-DhFiA8Ch.js → index-DRhoA51c.js} +10 -10
- package/dist/ui/assets/{index-D_IJsvkJ.css → index-SrfNUeBZ.css} +1 -1
- package/dist/ui/assets/{prompt-templates-CVzMex9L.js → prompt-templates-DhR4Qoy9.js} +4 -4
- package/dist/ui/assets/{useExperimentalActions-aI00AFZv.js → useExperimentalActions-BPDdswCN.js} +1 -1
- package/dist/ui/index.html +2 -2
- package/package.json +1 -1
- package/dist/daemon/effect-pass.js +0 -24
- package/dist/db/migrations/018_effect_cycles.sql +0 -109
- package/dist/effects/api-contract.js +0 -1
- package/dist/effects/engine.js +0 -574
- package/dist/effects/gate.js +0 -2
- package/dist/effects/index.js +0 -5
- package/dist/effects/legacy.js +0 -28
- package/dist/effects/registry.js +0 -152
- package/dist/effects/sql-observer.js +0 -238
- package/dist/effects/store.js +0 -181
- package/dist/effects/types.js +0 -2
- package/dist/query/api/effect-service.js +0 -308
- package/dist/query/api/esf-observations.js +0 -254
- package/dist/ui/assets/BriefsPage-DIU8iFjV.js +0 -2
- package/dist/ui/assets/CacheWriteSpikesChart-1Nfnf7OX.js +0 -1
- package/dist/ui/assets/Chip-s-32Dh3o.js +0 -1
- package/dist/ui/assets/ComposedChart-DUw8Y79v.js +0 -1
- package/dist/ui/assets/FlavorDecomposition-CkHX3M-U.js +0 -1
- package/dist/ui/assets/FrictionCell-X75fKRBK.js +0 -2
- package/dist/ui/assets/GlossaryPage-D1tYumqL.js +0 -1
- package/dist/ui/assets/HotSessionsPage-8HlRe0VB.js +0 -1
- package/dist/ui/assets/OverviewPage-DL4joBBE.js +0 -2
- package/dist/ui/assets/RecommendationsPage-NkfH17eB.js +0 -2
- package/dist/ui/assets/SessionDetailPage-Ceqt7poO.js +0 -1
- package/dist/ui/assets/SettingsPage-ZK4ii5h_.js +0 -25
- package/dist/ui/assets/SpendPercentileChip-CzY6h1gP.js +0 -1
- package/dist/ui/assets/TrendChart-BSLwlBiq.js +0 -1
- package/dist/ui/assets/WorkspaceDetailPage-DFOdEB39.js +0 -1
- package/dist/ui/assets/WorkspacesPage-Doct6meK.js +0 -1
- package/dist/ui/assets/chart-theme-DYgw_Tac.js +0 -1
- package/dist/ui/assets/graphicalItemSelectors-0-Ue_y1f.js +0 -1
- package/dist/ui/assets/rec-sessions-ggz9MYgP.js +0 -1
|
@@ -81,12 +81,12 @@ export const d2Detector = {
|
|
|
81
81
|
scopeKey: `D2|global|${formula.model}`,
|
|
82
82
|
category: "CONTEXT",
|
|
83
83
|
scope_workspace_id: null,
|
|
84
|
-
lever: "
|
|
84
|
+
lever: "/clear between unrelated tasks; split long work; avoid mid-task /compact.",
|
|
85
85
|
target_metric: "avg_context_per_turn",
|
|
86
86
|
modeled_savings_u_per_wk: savingsU,
|
|
87
87
|
modeled_formula: formula,
|
|
88
88
|
evidence: {
|
|
89
|
-
title: `
|
|
89
|
+
title: `Shorten sessions: ${n} long-context run${n === 1 ? "" : "s"} this week`,
|
|
90
90
|
qualifying_session_count: qualifying.length,
|
|
91
91
|
qualifying_turn_count: qualifyingTurnCount,
|
|
92
92
|
session_ids: sessionIds,
|
|
@@ -192,7 +192,7 @@ export const d4Detector = {
|
|
|
192
192
|
? {
|
|
193
193
|
withheld: true,
|
|
194
194
|
withheld_reason: `Sonnet weekly cap is the binding constraint (Sonnet util ${bindingSonnet.utilization} >= all-models ${perModelSnapshot.seven_day_util}) — routing Opus->Sonnet would worsen it`,
|
|
195
|
-
title: `
|
|
195
|
+
title: `[withheld] Route Opus→Sonnet: ${mismatchPct}% of turns are high-context low-output`,
|
|
196
196
|
}
|
|
197
197
|
: perModelSnapshot && sonnetEntries && sonnetEntries.length > 0
|
|
198
198
|
? { cap_attribution: "all_models_or_opus_binds" }
|
|
@@ -202,7 +202,7 @@ export const d4Detector = {
|
|
|
202
202
|
category: "MODEL",
|
|
203
203
|
scope_workspace_id: workspace_id,
|
|
204
204
|
// Advisory gate (W0.3): which cap binds is NOT inferable from JSONL. Conditional lever.
|
|
205
|
-
lever: "
|
|
205
|
+
lever: "If your all-models / Opus / 5h cap is the one binding — check /usage — these high-context low-output Opus turns are Sonnet-movable. This does NOT help, and can hurt, if your Sonnet-specific weekly cap is the binding constraint.",
|
|
206
206
|
target_metric: "model_mix_opus_fraction",
|
|
207
207
|
// Advisory gate: suppress the crisp $/wk headline until live /usage cap-attribution exists.
|
|
208
208
|
modeled_savings_u_per_wk: null,
|
|
@@ -210,7 +210,7 @@ export const d4Detector = {
|
|
|
210
210
|
// Destructure out result_usd_per_wk so the advisory formula carries no crisp $/wk figure.
|
|
211
211
|
modeled_formula: (({ result_usd_per_wk: _, ...rest }) => ({ ...rest, kind: "ADVISORY" }))(formula),
|
|
212
212
|
evidence: {
|
|
213
|
-
title: `
|
|
213
|
+
title: `Route Opus→Sonnet: ${mismatchPct}% of turns are high-context low-output`,
|
|
214
214
|
workspace_id,
|
|
215
215
|
total_opus_turns_per_week: totalOpus,
|
|
216
216
|
mismatch_turns_per_week: mismatchCount,
|
|
@@ -262,7 +262,7 @@ export const d6Detector = {
|
|
|
262
262
|
modeled_savings_u_per_wk: modeledSavingsU,
|
|
263
263
|
modeled_formula: formula,
|
|
264
264
|
evidence: {
|
|
265
|
-
title: `
|
|
265
|
+
title: `Trim tool output: ${Math.round(bloatShare * 100)}% bloat share in session`,
|
|
266
266
|
session_id: row.session_id,
|
|
267
267
|
workspace_id: row.workspace_id,
|
|
268
268
|
tool_result_bytes: row.tool_result_bytes,
|
|
@@ -299,7 +299,7 @@ export const d7Detector = {
|
|
|
299
299
|
expression: "cap-weighted exposure of turns owning repeat-excess events; not an avoidable-token or USD savings estimate",
|
|
300
300
|
},
|
|
301
301
|
evidence: {
|
|
302
|
-
title: `
|
|
302
|
+
title: `Break retry loops: ${flaggedTurns.size} flagged turn${flaggedTurns.size === 1 ? "" : "s"} in session`,
|
|
303
303
|
session_id: sessionId,
|
|
304
304
|
workspace_id: session.workspaceId,
|
|
305
305
|
loop_flagged_event_count: flaggedEventIds.size,
|
|
@@ -125,7 +125,7 @@ export const d8Detector = {
|
|
|
125
125
|
// TTL-regime facet: is this session dominated by 5m-tier creation?
|
|
126
126
|
const fiveMinCreation = events.reduce((s, e) => s + e.cache_write_5m, 0);
|
|
127
127
|
const regime5m = totalCreation > 0 && fiveMinCreation / totalCreation >= D8_REGIME_5M_SHARE;
|
|
128
|
-
const baseLever = "
|
|
128
|
+
const baseLever = "Use /clear (or resume-from-summary) before idling past the cache TTL, and batch prefix/CLAUDE.md edits to a session boundary so they don't invalidate the warm cache mid-session.";
|
|
129
129
|
const lever = regime5m
|
|
130
130
|
? `${baseLever} This session's creation is mostly 5m-tier — enable the 1h cache regime (ENABLE_PROMPT_CACHING_1H) where long pauses are unavoidable.`
|
|
131
131
|
: baseLever;
|
|
@@ -138,7 +138,7 @@ export const d8Detector = {
|
|
|
138
138
|
modeled_savings_u_per_wk: savingsU,
|
|
139
139
|
modeled_formula: formula,
|
|
140
140
|
evidence: {
|
|
141
|
-
title: `
|
|
141
|
+
title: `Reduce cache-write churn: ${events.length} re-write${events.length === 1 ? "" : "s"} in session`,
|
|
142
142
|
session_id: sessionId,
|
|
143
143
|
workspace_id,
|
|
144
144
|
churn_event_count: events.length,
|
|
@@ -62,7 +62,7 @@ export const d9Detector = {
|
|
|
62
62
|
modeled_savings_u_per_wk: null,
|
|
63
63
|
modeled_formula: d9Formula(sidechainCap, D9_UNPRODUCTIVE_FRACTION),
|
|
64
64
|
evidence: {
|
|
65
|
-
title: `Review background-
|
|
65
|
+
title: `Review background fan-out: ${Math.round(share * 100)}% cap-weighted sidechain`,
|
|
66
66
|
workspace_id: row.workspace_id,
|
|
67
67
|
sidechain_cap_weighted_tokens: sidechainCap,
|
|
68
68
|
total_cap_weighted_tokens: totalCap,
|
|
@@ -20,8 +20,6 @@
|
|
|
20
20
|
*
|
|
21
21
|
* Injected `now: Date` everywhere — never call new Date() in this module.
|
|
22
22
|
*/
|
|
23
|
-
import { ESF_EFFECT_WRITER_ENABLED } from "../effects/gate.js";
|
|
24
|
-
import { PREMIUM_MODEL_SQL } from "../ingest/pricing.js";
|
|
25
23
|
import { GLOBAL_WORKSPACE_ID } from "./context-probe.js";
|
|
26
24
|
import { isD1SourceBackedRecommendation, parseD1SourceIdentity } from "./d1-source-identity.js";
|
|
27
25
|
/** Wait ≥1 day after adoption before entering MEASURING. */
|
|
@@ -84,14 +82,14 @@ function avgFloorContext(db, scopeWorkspaceId, fromIso, toIso) {
|
|
|
84
82
|
/**
|
|
85
83
|
* D4 signal: routing-adherence score = ROUND(100 × (1 − premium_share)) over
|
|
86
84
|
* non-sidechain reconciled turns in [fromIso, toIso), where premium = model
|
|
87
|
-
*
|
|
85
|
+
* contains 'opus'. The data-model's "mechanical turn" classifier is not yet
|
|
88
86
|
* shipped, so all reconciled turns are the mechanical set (conservative proxy —
|
|
89
87
|
* documented in the W4 build digest).
|
|
90
88
|
*/
|
|
91
89
|
function routingAdherenceScore(db, scopeWorkspaceId, fromIso, toIso) {
|
|
92
90
|
const scoped = scopeWorkspaceId !== null;
|
|
93
91
|
const sql = `SELECT COUNT(*) AS n,
|
|
94
|
-
AVG(CASE WHEN
|
|
92
|
+
AVG(CASE WHEN model LIKE '%opus%' THEN 1.0 ELSE 0.0 END) AS premium_share
|
|
95
93
|
FROM turns
|
|
96
94
|
WHERE is_sidechain = 0 AND provisional = 0 AND ts >= ? AND ts < ? ${scoped ? "AND workspace_id = ?" : ""}`;
|
|
97
95
|
const params = scoped ? [fromIso, toIso, scopeWorkspaceId] : [fromIso, toIso];
|
|
@@ -136,8 +134,7 @@ function handlerFor(rec) {
|
|
|
136
134
|
}
|
|
137
135
|
/** Warning-class recs (D5 / LIMIT / explicit NONE) skip the lifecycle entirely (design §2d). */
|
|
138
136
|
export function isWarningClass(rec) {
|
|
139
|
-
return (rec.
|
|
140
|
-
rec.category === "LIMIT" ||
|
|
137
|
+
return (rec.category === "LIMIT" ||
|
|
141
138
|
rec.target_metric === "NONE" ||
|
|
142
139
|
rec.target_metric === "forecast_margin");
|
|
143
140
|
}
|
|
@@ -366,9 +363,6 @@ function closeMeasurement(db, rec, now, result) {
|
|
|
366
363
|
*/
|
|
367
364
|
export function runMeasurementPass(db, now, opts) {
|
|
368
365
|
const result = { to_measuring: 0, verdicts: 0, skipped: 0 };
|
|
369
|
-
// The versioned writer leaves every historical W4 row unchanged, including open rows.
|
|
370
|
-
if (ESF_EFFECT_WRITER_ENABLED)
|
|
371
|
-
return result;
|
|
372
366
|
if (!tableExists(db, "context_inventory_history")) {
|
|
373
367
|
console.warn("W4: context_inventory_history not available; measurement pass skipped");
|
|
374
368
|
return result;
|
|
@@ -386,16 +380,11 @@ export function runMeasurementPass(db, now, opts) {
|
|
|
386
380
|
}
|
|
387
381
|
}
|
|
388
382
|
try {
|
|
389
|
-
// A code-gate rollback must not let W4 take over an already-versioned recommendation.
|
|
390
|
-
const versionedExclusion = tableExists(db, "effect_cycles")
|
|
391
|
-
? "AND NOT EXISTS (SELECT 1 FROM effect_cycles c WHERE c.rec_id=recommendations.rec_id)"
|
|
392
|
-
: "";
|
|
393
383
|
const rows = db
|
|
394
384
|
.prepare(`SELECT rec_id, detector_id, category, scope_workspace_id, evidence_json,
|
|
395
385
|
target_metric, state, adopted_at
|
|
396
386
|
FROM recommendations
|
|
397
387
|
WHERE state IN ('ADOPTED', 'MEASURING') AND adopted_at IS NOT NULL
|
|
398
|
-
${versionedExclusion}
|
|
399
388
|
ORDER BY adopted_at ASC, rec_id ASC`)
|
|
400
389
|
.all();
|
|
401
390
|
for (const rec of rows) {
|
|
@@ -11,7 +11,6 @@
|
|
|
11
11
|
* committed). The hook writes no transcript content to stdout/stderr.
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
|
-
import { randomBytes } from "node:crypto";
|
|
15
14
|
import * as fs from "node:fs";
|
|
16
15
|
import * as os from "node:os";
|
|
17
16
|
import * as path from "node:path";
|
|
@@ -20,11 +19,9 @@ import { fileURLToPath } from "node:url";
|
|
|
20
19
|
|
|
21
20
|
const MAX_COUNT = 20;
|
|
22
21
|
const MAX_BYTES = 500 * 1024 * 1024; // 500 MiB across all snapshots
|
|
23
|
-
const MAX_COLLISION_ATTEMPTS = 16;
|
|
24
22
|
|
|
25
|
-
//
|
|
26
|
-
//
|
|
27
|
-
// same name; exclusive creation below is the final no-overwrite guard.
|
|
23
|
+
// Monotonic counter so two firings in the same millisecond get distinct filenames
|
|
24
|
+
// that still sort in write order (the timestamp dominates; this breaks same-ms ties).
|
|
28
25
|
let snapshotCounter = 0;
|
|
29
26
|
|
|
30
27
|
/** The directory snapshots are written to (overridable for tests). */
|
|
@@ -36,8 +33,7 @@ export function checkpointDir() {
|
|
|
36
33
|
export function snapshotName(sessionId, now) {
|
|
37
34
|
snapshotCounter = (snapshotCounter + 1) % 1_000_000;
|
|
38
35
|
const stamp = now.toISOString().replace(/[:.]/g, "-");
|
|
39
|
-
|
|
40
|
-
return `${encodeURIComponent(sessionId)}-${stamp}-${counter}-${randomBytes(6).toString("hex")}.jsonl`;
|
|
36
|
+
return `${encodeURIComponent(sessionId)}-${stamp}-${String(snapshotCounter).padStart(6, "0")}.jsonl`;
|
|
41
37
|
}
|
|
42
38
|
|
|
43
39
|
/**
|
|
@@ -47,22 +43,14 @@ export function snapshotName(sessionId, now) {
|
|
|
47
43
|
export function writeCheckpoint(transcriptPath, sessionId, dir, now = new Date()) {
|
|
48
44
|
if (typeof transcriptPath !== "string" || !fs.existsSync(transcriptPath)) return null;
|
|
49
45
|
fs.mkdirSync(dir, { recursive: true });
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
throw error;
|
|
57
|
-
}
|
|
58
|
-
try {
|
|
59
|
-
fs.chmodSync(dest, 0o600);
|
|
60
|
-
} catch {
|
|
61
|
-
// chmod is a no-op boundary on Windows; the user-profile ACL is the equivalent guard.
|
|
62
|
-
}
|
|
63
|
-
return dest;
|
|
46
|
+
const dest = path.join(dir, snapshotName(sessionId, now));
|
|
47
|
+
fs.copyFileSync(transcriptPath, dest);
|
|
48
|
+
try {
|
|
49
|
+
fs.chmodSync(dest, 0o600);
|
|
50
|
+
} catch {
|
|
51
|
+
// chmod is a no-op boundary on Windows; the user-profile ACL is the equivalent guard.
|
|
64
52
|
}
|
|
65
|
-
return
|
|
53
|
+
return dest;
|
|
66
54
|
}
|
|
67
55
|
|
|
68
56
|
/**
|
|
@@ -70,10 +58,9 @@ export function writeCheckpoint(transcriptPath, sessionId, dir, now = new Date()
|
|
|
70
58
|
* not create. Malformed .jsonl files are deliberately left alone by retention.
|
|
71
59
|
*/
|
|
72
60
|
function snapshotTimestamp(name) {
|
|
73
|
-
const match =
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
);
|
|
61
|
+
const match = /-(\d{4})-(\d{2})-(\d{2})T(\d{2})-(\d{2})-(\d{2})-(\d{3})Z-\d{6}\.jsonl$/.exec(
|
|
62
|
+
name,
|
|
63
|
+
);
|
|
77
64
|
if (!match) return null;
|
|
78
65
|
const [, year, month, day, hour, minute, second, millisecond] = match;
|
|
79
66
|
const timestamp = Date.parse(
|
package/dist/ingest/pricing.js
CHANGED
|
@@ -62,21 +62,6 @@ export function modelTier(model) {
|
|
|
62
62
|
}
|
|
63
63
|
return null;
|
|
64
64
|
}
|
|
65
|
-
/**
|
|
66
|
-
* SQL predicate matching premium-priced models on a `model` column: Opus plus
|
|
67
|
-
* the Fable/Mythos families (Fable's $10/$50 base is 2× Opus). Shared by every
|
|
68
|
-
* premium-share aggregate so the family list can't drift between queries.
|
|
69
|
-
*/
|
|
70
|
-
export const PREMIUM_MODEL_SQL = "(model LIKE '%opus%' OR model LIKE '%fable%' OR model LIKE '%mythos%')";
|
|
71
|
-
/**
|
|
72
|
-
* Input list price ($/MTok) for a model, for ordering model lists
|
|
73
|
-
* most-expensive first. Derived from LIST_PRICES via modelTier so a new tier
|
|
74
|
-
* automatically sorts by its price; unpriceable models return 0 (sort last).
|
|
75
|
-
*/
|
|
76
|
-
export function modelInputListPrice(model) {
|
|
77
|
-
const tier = modelTier(model);
|
|
78
|
-
return tier === null ? 0 : (LIST_PRICES[tier]?.[0] ?? 0);
|
|
79
|
-
}
|
|
80
65
|
function addDaysIso(iso, days) {
|
|
81
66
|
const d = new Date(iso);
|
|
82
67
|
d.setUTCDate(d.getUTCDate() + days);
|
|
@@ -21,7 +21,6 @@ export function getCostPerSuccess(db, workspaceId, from, to) {
|
|
|
21
21
|
to,
|
|
22
22
|
workspaceId,
|
|
23
23
|
workspaceId,
|
|
24
|
-
workspaceId,
|
|
25
24
|
from,
|
|
26
25
|
to,
|
|
27
26
|
workspaceId,
|
|
@@ -47,14 +46,6 @@ export function getCostPerSuccess(db, workspaceId, from, to) {
|
|
|
47
46
|
JOIN merged_work_items mwi ON mwi.work_item_id = swl.work_item_id
|
|
48
47
|
WHERE 1 = 1${sessionFilter}
|
|
49
48
|
),
|
|
50
|
-
merged_session_links AS (
|
|
51
|
-
SELECT swl.session_id, COUNT(DISTINCT swl.work_item_id) AS merged_pr_links
|
|
52
|
-
FROM session_work_links swl
|
|
53
|
-
JOIN merged_work_items mwi ON mwi.work_item_id = swl.work_item_id
|
|
54
|
-
JOIN sessions s ON s.session_id = swl.session_id
|
|
55
|
-
WHERE 1 = 1${sessionFilter}
|
|
56
|
-
GROUP BY swl.session_id
|
|
57
|
-
),
|
|
58
49
|
in_window_sessions AS (
|
|
59
50
|
SELECT s.session_id, s.cost_equiv_u
|
|
60
51
|
FROM sessions s
|
|
@@ -73,8 +64,6 @@ export function getCostPerSuccess(db, workspaceId, from, to) {
|
|
|
73
64
|
SELECT
|
|
74
65
|
(SELECT COUNT(*) FROM merged_work_items) AS merged_pr_count,
|
|
75
66
|
(SELECT COUNT(*) FROM closed_unmerged_work_items) AS closed_unmerged_count,
|
|
76
|
-
(SELECT COUNT(*) FROM merged_session_links) AS unique_linked_session_count,
|
|
77
|
-
(SELECT COUNT(*) FROM merged_session_links WHERE merged_pr_links > 1) AS shared_linked_session_count,
|
|
78
67
|
CASE WHEN (SELECT COUNT(*) FROM merged_work_items) = 0 THEN NULL
|
|
79
68
|
ELSE (SELECT COALESCE(SUM(cost_equiv_u), 0) FROM merged_session_costs) * 1.0
|
|
80
69
|
/ (SELECT COUNT(*) FROM merged_work_items)
|
|
@@ -102,14 +91,13 @@ export function getCostPerSuccess(db, workspaceId, from, to) {
|
|
|
102
91
|
};
|
|
103
92
|
return buildResponse(data, {
|
|
104
93
|
claim_kind: "OBS_PROXY",
|
|
105
|
-
metric_definition_version: "esf-1",
|
|
106
94
|
n: data.merged_pr_count,
|
|
107
95
|
window: { from, to },
|
|
108
96
|
qualification: {
|
|
109
97
|
provisional_excluded: false,
|
|
110
98
|
unpriced_turns: 0,
|
|
111
99
|
claim_kinds_count: 1,
|
|
112
|
-
note: "Directional (OBS_PROXY):
|
|
100
|
+
note: "Directional (OBS_PROXY): survivorship bias (heavy-spend sessions that never open a PR are invisible); reviewer-dependence (merge is a human decision, not a quality guarantee); linkage-coverage cap (only linkage_coverage_pct% of in-window sessions are linked to a PR, so unlinked spend is excluded). cost_per_merged_pr_u uses lifecycle attribution: each merged PR carries the full cost of every linked session whenever it ran, so narrowing the window changes the PR population, not the per-PR cost.",
|
|
113
101
|
},
|
|
114
102
|
...(workspaceId === null ? {} : { drilldown_ids: { workspace_id: workspaceId } }),
|
|
115
103
|
});
|
|
@@ -44,20 +44,8 @@ export function getDeliveryMetrics(db, opts) {
|
|
|
44
44
|
FROM tool_events te
|
|
45
45
|
WHERE te.session_id = iws.session_id
|
|
46
46
|
AND te.commit_sha IS NOT NULL
|
|
47
|
-
)
|
|
48
|
-
s.state = 'LIVE'
|
|
49
|
-
AND EXISTS (
|
|
50
|
-
SELECT 1 FROM tool_events te
|
|
51
|
-
WHERE te.session_id = iws.session_id
|
|
52
|
-
AND te.tool_name IN ('Bash', 'Edit', 'Write', 'NotebookEdit')
|
|
53
|
-
)
|
|
54
|
-
AND NOT EXISTS (
|
|
55
|
-
SELECT 1 FROM tool_events te
|
|
56
|
-
WHERE te.session_id = iws.session_id
|
|
57
|
-
AND te.commit_sha IS NOT NULL
|
|
58
|
-
) AS is_live_session
|
|
47
|
+
) AS is_abandoned_session
|
|
59
48
|
FROM in_window_sessions iws
|
|
60
|
-
JOIN sessions s USING (session_id)
|
|
61
49
|
),
|
|
62
50
|
session_spend AS (
|
|
63
51
|
SELECT session_id,
|
|
@@ -79,16 +67,7 @@ export function getDeliveryMetrics(db, opts) {
|
|
|
79
67
|
CASE WHEN COALESCE(SUM(ss.spend_u), 0) = 0 THEN NULL
|
|
80
68
|
ELSE SUM(CASE WHEN sf.is_abandoned_session THEN ss.spend_u ELSE 0 END) * 1.0
|
|
81
69
|
/ SUM(ss.spend_u)
|
|
82
|
-
END AS abandoned_spend_share
|
|
83
|
-
COALESCE(SUM(sf.is_abandoned_session), 0) AS no_commit_activity_session_count,
|
|
84
|
-
COALESCE(SUM(CASE WHEN sf.is_abandoned_session THEN ss.spend_u ELSE 0 END), 0)
|
|
85
|
-
AS no_commit_activity_spend_u,
|
|
86
|
-
CASE WHEN COALESCE(SUM(ss.spend_u), 0) = 0 THEN NULL
|
|
87
|
-
ELSE SUM(CASE WHEN sf.is_abandoned_session THEN ss.spend_u ELSE 0 END) * 1.0
|
|
88
|
-
/ SUM(ss.spend_u)
|
|
89
|
-
END AS no_commit_activity_spend_share,
|
|
90
|
-
COALESCE(SUM(CASE WHEN sf.is_live_session THEN 1 ELSE 0 END), 0)
|
|
91
|
-
AS live_session_excluded_from_no_commit_activity_count
|
|
70
|
+
END AS abandoned_spend_share
|
|
92
71
|
FROM session_flags sf
|
|
93
72
|
JOIN session_spend ss USING (session_id)`)
|
|
94
73
|
.get(...params);
|
|
@@ -100,14 +79,13 @@ export function getDeliveryMetrics(db, opts) {
|
|
|
100
79
|
};
|
|
101
80
|
return buildResponse(data, {
|
|
102
81
|
claim_kind: "OBS_PROXY",
|
|
103
|
-
metric_definition_version: "esf-1",
|
|
104
82
|
n: data.commit_session_count,
|
|
105
83
|
window: { from: opts.from, to: opts.to },
|
|
106
84
|
qualification: {
|
|
107
85
|
provisional_excluded: true,
|
|
108
86
|
unpriced_turns: 0,
|
|
109
87
|
claim_kinds_count: 1,
|
|
110
|
-
note: "Commit SHA presence is an observed delivery proxy.
|
|
88
|
+
note: "Commit SHA presence is an observed delivery proxy.",
|
|
111
89
|
},
|
|
112
90
|
...(opts.workspaceId === null ? {} : { drilldown_ids: { workspace_id: opts.workspaceId } }),
|
|
113
91
|
});
|
|
@@ -34,17 +34,11 @@ export function computeSessionDelivery(db, sessionId) {
|
|
|
34
34
|
turns_to_first_commit = countRow.n;
|
|
35
35
|
}
|
|
36
36
|
const sessRow = db
|
|
37
|
-
.prepare(
|
|
38
|
-
EXISTS (SELECT 1 FROM tool_events te WHERE te.session_id = sessions.session_id
|
|
39
|
-
AND te.tool_name IN ('Bash','Write','Edit','NotebookEdit')) AS has_activity
|
|
40
|
-
FROM sessions WHERE session_id = ?`)
|
|
37
|
+
.prepare("SELECT user_turn_count, state FROM sessions WHERE session_id = ?")
|
|
41
38
|
.get(sessionId);
|
|
42
39
|
const user_turn_count = sessRow?.user_turn_count ?? 0;
|
|
43
40
|
const state = sessRow?.state ?? "";
|
|
44
|
-
const deep_abandoned = user_turn_count >= 10 &&
|
|
45
|
-
firstCommitTs === null &&
|
|
46
|
-
state === "RECONCILED" &&
|
|
47
|
-
sessRow?.has_activity === 1;
|
|
41
|
+
const deep_abandoned = user_turn_count >= 10 && firstCommitTs === null && state === "RECONCILED";
|
|
48
42
|
return { turns_to_first_commit, deep_abandoned };
|
|
49
43
|
}
|
|
50
44
|
// ---------------------------------------------------------------------------
|
|
@@ -89,9 +83,8 @@ export function getAbandonedSpendSplit(db, opts) {
|
|
|
89
83
|
FROM tool_events te
|
|
90
84
|
WHERE te.session_id = iws.session_id
|
|
91
85
|
AND te.commit_sha IS NOT NULL
|
|
92
|
-
)
|
|
86
|
+
) AS is_abandoned_session
|
|
93
87
|
FROM in_window_sessions iws
|
|
94
|
-
JOIN sessions s USING (session_id)
|
|
95
88
|
),
|
|
96
89
|
session_spend AS (
|
|
97
90
|
SELECT session_id,
|
|
@@ -152,24 +145,15 @@ export function getClosureProxy(db, opts) {
|
|
|
152
145
|
unresolved_count: 0,
|
|
153
146
|
pending_count: 0,
|
|
154
147
|
resolved_share: null,
|
|
155
|
-
no_later_workspace_session_count: 0,
|
|
156
|
-
later_workspace_session_count: 0,
|
|
157
|
-
no_later_workspace_session_share: null,
|
|
158
148
|
window_hours: windowHours,
|
|
159
149
|
workspace_id: opts.workspaceId,
|
|
160
150
|
});
|
|
161
151
|
if (candidates.length === 0) {
|
|
162
152
|
return buildResponse(emptyProxy(), {
|
|
163
153
|
claim_kind: "EXPERIMENTAL",
|
|
164
|
-
metric_definition_version: "esf-1",
|
|
165
154
|
n: 0,
|
|
166
155
|
window: { from: opts.now, to: opts.now },
|
|
167
|
-
qualification: {
|
|
168
|
-
provisional_excluded: false,
|
|
169
|
-
unpriced_turns: 0,
|
|
170
|
-
claim_kinds_count: 1,
|
|
171
|
-
note: `${note} No later workspace session is an observation, not task resolution.`,
|
|
172
|
-
},
|
|
156
|
+
qualification: { provisional_excluded: false, unpriced_turns: 0, claim_kinds_count: 1, note },
|
|
173
157
|
...(opts.workspaceId === null ? {} : { drilldown_ids: { workspace_id: opts.workspaceId } }),
|
|
174
158
|
});
|
|
175
159
|
}
|
|
@@ -220,23 +204,14 @@ export function getClosureProxy(db, opts) {
|
|
|
220
204
|
unresolved_count,
|
|
221
205
|
pending_count,
|
|
222
206
|
resolved_share,
|
|
223
|
-
no_later_workspace_session_count: resolved_count,
|
|
224
|
-
later_workspace_session_count: unresolved_count,
|
|
225
|
-
no_later_workspace_session_share: resolved_share,
|
|
226
207
|
window_hours: windowHours,
|
|
227
208
|
workspace_id: opts.workspaceId,
|
|
228
209
|
};
|
|
229
210
|
return buildResponse(proxy, {
|
|
230
211
|
claim_kind: "EXPERIMENTAL",
|
|
231
|
-
metric_definition_version: "esf-1",
|
|
232
212
|
n: no_commit_session_count,
|
|
233
213
|
window: { from: opts.now, to: opts.now },
|
|
234
|
-
qualification: {
|
|
235
|
-
provisional_excluded: false,
|
|
236
|
-
unpriced_turns: 0,
|
|
237
|
-
claim_kinds_count: 1,
|
|
238
|
-
note: `${note} "Resolved" and "unresolved" are legacy aliases for no later workspace session and a later workspace session; neither establishes task resolution.`,
|
|
239
|
-
},
|
|
214
|
+
qualification: { provisional_excluded: false, unpriced_turns: 0, claim_kinds_count: 1, note },
|
|
240
215
|
...(opts.workspaceId === null ? {} : { drilldown_ids: { workspace_id: opts.workspaceId } }),
|
|
241
216
|
});
|
|
242
217
|
}
|
|
@@ -37,19 +37,22 @@ export function getEfficiencyHeadroom(db, opts) {
|
|
|
37
37
|
FROM turns
|
|
38
38
|
WHERE ts >= ? AND ts < ? AND provisional = 0`)
|
|
39
39
|
.get(from, to);
|
|
40
|
-
const opportunities = db
|
|
41
|
-
.prepare(`SELECT rec_id, modeled_savings_u_per_wk
|
|
42
|
-
FROM recommendations
|
|
43
|
-
WHERE state IN ('PROPOSED', 'ADOPTED') AND modeled_savings_u_per_wk IS NOT NULL
|
|
44
|
-
ORDER BY rec_id`)
|
|
45
|
-
.all();
|
|
46
40
|
const headroom_u_per_wk = recs.sum_u ?? 0;
|
|
47
41
|
const actual_u_per_wk = spend.actual_u;
|
|
48
|
-
|
|
49
|
-
//
|
|
50
|
-
|
|
42
|
+
const openRecsExist = recs.open_total > 0;
|
|
43
|
+
// Null when: no spend (avoid /0 → ∞/NaN), or open recs exist but none carries a
|
|
44
|
+
// savings figure (an all-null modeled set is not a defensible ceiling).
|
|
45
|
+
let headroom_pct;
|
|
46
|
+
if (actual_u_per_wk === 0) {
|
|
47
|
+
headroom_pct = null;
|
|
48
|
+
}
|
|
49
|
+
else if (openRecsExist && recs.with_savings === 0) {
|
|
50
|
+
headroom_pct = null;
|
|
51
|
+
}
|
|
52
|
+
else {
|
|
53
|
+
headroom_pct = headroom_u_per_wk / actual_u_per_wk;
|
|
54
|
+
}
|
|
51
55
|
const data = {
|
|
52
|
-
opportunities,
|
|
53
56
|
headroom_u_per_wk,
|
|
54
57
|
actual_u_per_wk,
|
|
55
58
|
headroom_pct,
|
|
@@ -59,14 +62,13 @@ export function getEfficiencyHeadroom(db, opts) {
|
|
|
59
62
|
};
|
|
60
63
|
return buildResponse(data, {
|
|
61
64
|
claim_kind: "EXPERIMENTAL",
|
|
62
|
-
metric_definition_version: "esf-1",
|
|
63
65
|
n: recs.with_savings,
|
|
64
66
|
window: { from, to },
|
|
65
67
|
qualification: {
|
|
66
68
|
provisional_excluded: true,
|
|
67
69
|
unpriced_turns: 0,
|
|
68
70
|
claim_kinds_count: 1,
|
|
69
|
-
note: "
|
|
71
|
+
note: "Modeled headroom — if every open recommendation were applied; built from unvalidated per-detector fractions.",
|
|
70
72
|
},
|
|
71
73
|
});
|
|
72
74
|
}
|
|
@@ -11,7 +11,6 @@
|
|
|
11
11
|
*
|
|
12
12
|
* SQL references: Data Model v2 §2 lines 261-275 / 303-314 / 329-334.
|
|
13
13
|
*/
|
|
14
|
-
import { PREMIUM_MODEL_SQL } from "../../ingest/pricing.js";
|
|
15
14
|
import { getQueryDb } from "../db-context.js";
|
|
16
15
|
import { buildResponse } from "../envelope.js";
|
|
17
16
|
// ---------------------------------------------------------------------------
|
|
@@ -137,7 +136,7 @@ export function listWorkspaceOutcomes() {
|
|
|
137
136
|
const lrByWs = new Map(lrRows.map((r) => [r.workspace_id, r.linkage_rate]));
|
|
138
137
|
const adherenceRows = db
|
|
139
138
|
.prepare(`SELECT workspace_id, COUNT(*) AS n,
|
|
140
|
-
AVG(CASE WHEN
|
|
139
|
+
AVG(CASE WHEN model LIKE '%opus%' THEN 1.0 ELSE 0.0 END) AS premium_share
|
|
141
140
|
FROM turns
|
|
142
141
|
WHERE is_sidechain = 0 AND provisional = 0
|
|
143
142
|
GROUP BY workspace_id`)
|