offhands 0.1.18 → 0.1.19
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/dist/daemon.mjs +95 -11
- package/package.json +1 -1
package/dist/daemon.mjs
CHANGED
|
@@ -44206,6 +44206,7 @@ import { createServer as createServer3 } from "node:net";
|
|
|
44206
44206
|
import { tmpdir as tmpdir4 } from "node:os";
|
|
44207
44207
|
import { join as join13, resolve as resolve5 } from "node:path";
|
|
44208
44208
|
var MIN_PAIRS_FOR_CLAIM = 10;
|
|
44209
|
+
var USAGE_GRACE_MS = 2e3;
|
|
44209
44210
|
var median2 = (xs) => {
|
|
44210
44211
|
if (xs.length === 0) return null;
|
|
44211
44212
|
const s2 = [...xs].sort((a2, b2) => a2 - b2);
|
|
@@ -44225,6 +44226,33 @@ function signTestP(a2, b2) {
|
|
|
44225
44226
|
}
|
|
44226
44227
|
return Math.min(1, 2 * cumulative / 2 ** n3);
|
|
44227
44228
|
}
|
|
44229
|
+
function signedRankP(diffs) {
|
|
44230
|
+
const d2 = diffs.filter((x2) => x2 !== 0);
|
|
44231
|
+
const n3 = d2.length;
|
|
44232
|
+
if (n3 === 0) return null;
|
|
44233
|
+
const order = d2.map((x2, i2) => ({ a: Math.abs(x2), i: i2 })).sort((p2, q2) => p2.a - q2.a);
|
|
44234
|
+
const rank2 = new Array(n3);
|
|
44235
|
+
for (let lo = 0; lo < n3; ) {
|
|
44236
|
+
let hi = lo;
|
|
44237
|
+
while (hi + 1 < n3 && order[hi + 1].a === order[lo].a) hi++;
|
|
44238
|
+
const avgRank2 = lo + 1 + (hi + 1);
|
|
44239
|
+
for (let k2 = lo; k2 <= hi; k2++) rank2[order[k2].i] = avgRank2;
|
|
44240
|
+
lo = hi + 1;
|
|
44241
|
+
}
|
|
44242
|
+
const total2 = rank2.reduce((s2, r2) => s2 + r2, 0);
|
|
44243
|
+
const observed2 = d2.reduce((s2, x2, i2) => s2 + (x2 > 0 ? rank2[i2] : 0), 0);
|
|
44244
|
+
let counts = new Array(total2 + 1).fill(0);
|
|
44245
|
+
counts[0] = 1;
|
|
44246
|
+
for (const r2 of rank2) {
|
|
44247
|
+
const next = counts.slice();
|
|
44248
|
+
for (let s2 = 0; s2 + r2 <= total2; s2++) if (counts[s2]) next[s2 + r2] += counts[s2];
|
|
44249
|
+
counts = next;
|
|
44250
|
+
}
|
|
44251
|
+
const dev = Math.abs(2 * observed2 - total2);
|
|
44252
|
+
let extreme = 0;
|
|
44253
|
+
for (let s2 = 0; s2 <= total2; s2++) if (Math.abs(2 * s2 - total2) >= dev) extreme += counts[s2];
|
|
44254
|
+
return Math.min(1, extreme / 2 ** n3);
|
|
44255
|
+
}
|
|
44228
44256
|
function recallOf(answer, expect) {
|
|
44229
44257
|
if (!expect || expect.length === 0) return null;
|
|
44230
44258
|
const hay = answer.toLowerCase().replace(/\\/g, "/");
|
|
@@ -44241,10 +44269,12 @@ function summarize(name, pairs, pick) {
|
|
|
44241
44269
|
medianContext: median2(both.map((x2) => x2.c)),
|
|
44242
44270
|
medianPlain: median2(both.map((x2) => x2.q)),
|
|
44243
44271
|
medianDiff: median2(both.map((x2) => x2.c - x2.q)),
|
|
44272
|
+
meanDiff: mean(both.map((x2) => x2.c - x2.q)),
|
|
44244
44273
|
contextLower,
|
|
44245
44274
|
plainLower,
|
|
44246
44275
|
ties: both.length - contextLower - plainLower,
|
|
44247
|
-
p: both.length ? signTestP(contextLower, plainLower) : null
|
|
44276
|
+
p: both.length ? signTestP(contextLower, plainLower) : null,
|
|
44277
|
+
pSignedRank: signedRankP(both.map((x2) => x2.c - x2.q))
|
|
44248
44278
|
};
|
|
44249
44279
|
}
|
|
44250
44280
|
function analyzePairs(pairs, prompts) {
|
|
@@ -44286,11 +44316,12 @@ function analyzePairs(pairs, prompts) {
|
|
|
44286
44316
|
const enough = usable.length >= MIN_PAIRS_FOR_CLAIM;
|
|
44287
44317
|
for (const m3 of metrics) {
|
|
44288
44318
|
if (m3.n === 0) continue;
|
|
44289
|
-
const dir = m3.
|
|
44290
|
-
|
|
44291
|
-
|
|
44319
|
+
const dir = (m3.meanDiff ?? 0) < 0 ? "lower" : "higher";
|
|
44320
|
+
const ps = `signed-rank p=${m3.pSignedRank === null ? "\u2014" : m3.pSignedRank.toFixed(3)}, sign-test p=${m3.p === null ? "\u2014" : m3.p.toFixed(3)}`;
|
|
44321
|
+
if (enough && m3.n >= MIN_PAIRS_FOR_CLAIM && m3.pSignedRank !== null && m3.pSignedRank < 0.05) {
|
|
44322
|
+
conclusions.push(`${m3.name}: ${dir} with context (mean difference ${(m3.meanDiff ?? 0).toFixed(2)}; ${ps}; ${m3.contextLower} lower / ${m3.plainLower} higher / ${m3.ties} tied) \u2014 a measurable difference in this sample.`);
|
|
44292
44323
|
} else {
|
|
44293
|
-
conclusions.push(`${m3.name}: no reliable difference detected (${m3.contextLower} lower with context, ${m3.plainLower} lower without, ${m3.ties} tied;
|
|
44324
|
+
conclusions.push(`${m3.name}: no reliable difference detected (${m3.contextLower} lower with context, ${m3.plainLower} lower without, ${m3.ties} tied; ${ps}).`);
|
|
44294
44325
|
}
|
|
44295
44326
|
}
|
|
44296
44327
|
if (usable.length === 0 && errorMessages.length > 0) {
|
|
@@ -44332,17 +44363,17 @@ function formatAbReport(r2, meta) {
|
|
|
44332
44363
|
for (const m3 of r2.errorMessages.slice(0, 3)) L2.push(` ${m3.runs} run(s): ${m3.message}`);
|
|
44333
44364
|
}
|
|
44334
44365
|
L2.push("");
|
|
44335
|
-
L2.push("Metric with ctx plain median diff ctx lower / plain lower / tie sign-test p");
|
|
44366
|
+
L2.push("Metric with ctx plain median diff mean diff ctx lower / plain lower / tie signed-rank p sign-test p");
|
|
44336
44367
|
for (const m3 of r2.metrics) {
|
|
44337
44368
|
const digits = m3.name.startsWith("Cost") ? 4 : m3.name.startsWith("Duration") ? 0 : 1;
|
|
44338
44369
|
L2.push(
|
|
44339
|
-
`${m3.name.padEnd(19)} ${fmt(m3.medianContext, digits).padEnd(10)} ${fmt(m3.medianPlain, digits).padEnd(9)} ${fmt(m3.medianDiff, digits).padEnd(
|
|
44370
|
+
`${m3.name.padEnd(19)} ${fmt(m3.medianContext, digits).padEnd(10)} ${fmt(m3.medianPlain, digits).padEnd(9)} ${fmt(m3.medianDiff, digits).padEnd(12)} ${fmt(m3.meanDiff, digits).padEnd(11)} ${`${m3.contextLower} / ${m3.plainLower} / ${m3.ties}`.padEnd(30)} ${(m3.pSignedRank === null ? "\u2014" : m3.pSignedRank.toFixed(3)).padEnd(15)} ${m3.p === null ? "\u2014" : m3.p.toFixed(3)} (n=${m3.n})`
|
|
44340
44371
|
);
|
|
44341
44372
|
}
|
|
44342
44373
|
const q2 = r2.quality;
|
|
44343
44374
|
if (q2.pairsWithGroundTruth > 0) {
|
|
44344
44375
|
L2.push(
|
|
44345
|
-
`${"Answer recall".padEnd(19)} ${fmt(q2.meanRecallContext, 2).padEnd(10)} ${fmt(q2.meanRecallPlain, 2).padEnd(9)} ${"".padEnd(
|
|
44376
|
+
`${"Answer recall".padEnd(19)} ${fmt(q2.meanRecallContext, 2).padEnd(10)} ${fmt(q2.meanRecallPlain, 2).padEnd(9)} ${"".padEnd(12)} ${"".padEnd(11)} ${`${q2.contextHigher} / ${q2.plainHigher} / ${q2.ties}`.padEnd(30)} ${"".padEnd(15)} ${q2.p === null ? "\u2014" : q2.p.toFixed(3)} (n=${q2.pairsWithGroundTruth}; higher is better)`
|
|
44346
44377
|
);
|
|
44347
44378
|
} else {
|
|
44348
44379
|
L2.push("Answer recall not measured (no prompt listed expected files)");
|
|
@@ -44351,7 +44382,7 @@ function formatAbReport(r2, meta) {
|
|
|
44351
44382
|
for (const c2 of r2.conclusions) L2.push(`\u2022 ${c2}`);
|
|
44352
44383
|
L2.push("");
|
|
44353
44384
|
L2.push(
|
|
44354
|
-
"One model, one repository, this prompt set.
|
|
44385
|
+
"One model, one repository, this prompt set. Exact sign test and signed-rank test on paired runs; no savings figure is computed or implied, and none should be quoted from this."
|
|
44355
44386
|
);
|
|
44356
44387
|
return L2.join("\n");
|
|
44357
44388
|
}
|
|
@@ -44462,6 +44493,7 @@ async function createAbHarness(cfg) {
|
|
|
44462
44493
|
let detach = () => {
|
|
44463
44494
|
};
|
|
44464
44495
|
let timer;
|
|
44496
|
+
let doneAt;
|
|
44465
44497
|
const finish = () => {
|
|
44466
44498
|
if (settled) return;
|
|
44467
44499
|
settled = true;
|
|
@@ -44473,7 +44505,8 @@ async function createAbHarness(cfg) {
|
|
|
44473
44505
|
timedOut,
|
|
44474
44506
|
...error ? { error } : {},
|
|
44475
44507
|
toolCalls,
|
|
44476
|
-
|
|
44508
|
+
// Measured to the agent's own `done`, not to when we stopped listening.
|
|
44509
|
+
durationMs: (doneAt ?? Date.now()) - t0,
|
|
44477
44510
|
costUsd,
|
|
44478
44511
|
contextTokens,
|
|
44479
44512
|
contextAttached,
|
|
@@ -44483,6 +44516,10 @@ async function createAbHarness(cfg) {
|
|
|
44483
44516
|
};
|
|
44484
44517
|
const sink = (m3) => {
|
|
44485
44518
|
if (m3.sessionId !== session.id) return;
|
|
44519
|
+
if (m3.type === "receipt" && doneAt !== void 0) {
|
|
44520
|
+
finish();
|
|
44521
|
+
return;
|
|
44522
|
+
}
|
|
44486
44523
|
if (m3.type === "intelligence-review") {
|
|
44487
44524
|
contextAttached = true;
|
|
44488
44525
|
filesSelected = m3.filesSelected;
|
|
@@ -44502,7 +44539,10 @@ async function createAbHarness(cfg) {
|
|
|
44502
44539
|
} else if (e.type === "error") {
|
|
44503
44540
|
error = String(e.message ?? "error");
|
|
44504
44541
|
finish();
|
|
44505
|
-
} else if (e.type === "done")
|
|
44542
|
+
} else if (e.type === "done") {
|
|
44543
|
+
doneAt = Date.now();
|
|
44544
|
+
setTimeout(finish, USAGE_GRACE_MS);
|
|
44545
|
+
}
|
|
44506
44546
|
};
|
|
44507
44547
|
detach = manager2.attach(sink);
|
|
44508
44548
|
timer = setTimeout(() => {
|
|
@@ -44525,7 +44565,51 @@ function flag(args2, name) {
|
|
|
44525
44565
|
const i2 = args2.indexOf(name);
|
|
44526
44566
|
return i2 >= 0 ? args2[i2 + 1] : void 0;
|
|
44527
44567
|
}
|
|
44568
|
+
function intelAbMerge(args2, mergeAt, out) {
|
|
44569
|
+
const files = args2.slice(mergeAt + 1).filter((a2) => !a2.startsWith("--"));
|
|
44570
|
+
if (files.length === 0) {
|
|
44571
|
+
out("usage: offhands intel-ab --merge <result.json> [<result.json> ...] [--json]");
|
|
44572
|
+
return 1;
|
|
44573
|
+
}
|
|
44574
|
+
const prompts = [];
|
|
44575
|
+
const pairs = [];
|
|
44576
|
+
const workspaces = [];
|
|
44577
|
+
let runner = "";
|
|
44578
|
+
let model;
|
|
44579
|
+
for (const f2 of files) {
|
|
44580
|
+
let saved;
|
|
44581
|
+
try {
|
|
44582
|
+
saved = JSON.parse(readFileSync6(resolve5(f2), "utf8"));
|
|
44583
|
+
} catch (e) {
|
|
44584
|
+
out(`could not read ${f2}: ${e instanceof Error ? e.message : String(e)}`);
|
|
44585
|
+
return 1;
|
|
44586
|
+
}
|
|
44587
|
+
if (!Array.isArray(saved?.prompts) || !Array.isArray(saved?.pairs)) {
|
|
44588
|
+
out(`${f2} is not an intel-ab --out result (needs "prompts" and "pairs")`);
|
|
44589
|
+
return 1;
|
|
44590
|
+
}
|
|
44591
|
+
const offset = prompts.length;
|
|
44592
|
+
prompts.push(...saved.prompts);
|
|
44593
|
+
for (const p2 of saved.pairs) pairs.push({ ...p2, promptIndex: p2.promptIndex + offset });
|
|
44594
|
+
workspaces.push(String(saved.workspace ?? f2));
|
|
44595
|
+
runner = runner || String(saved.runnerId ?? "");
|
|
44596
|
+
model = model ?? saved.model;
|
|
44597
|
+
}
|
|
44598
|
+
const report = analyzePairs(pairs, prompts);
|
|
44599
|
+
out(
|
|
44600
|
+
args2.includes("--json") ? JSON.stringify(report, null, 2) : formatAbReport(report, {
|
|
44601
|
+
workspace: workspaces.join(" + "),
|
|
44602
|
+
runner: runner || "unknown",
|
|
44603
|
+
...model ? { model } : {},
|
|
44604
|
+
prompts: prompts.length,
|
|
44605
|
+
repeats: Math.max(1, Math.round(pairs.length / Math.max(prompts.length, 1)))
|
|
44606
|
+
})
|
|
44607
|
+
);
|
|
44608
|
+
return 0;
|
|
44609
|
+
}
|
|
44528
44610
|
async function intelAbCli(args2, out = console.log, deps = {}) {
|
|
44611
|
+
const mergeAt = args2.indexOf("--merge");
|
|
44612
|
+
if (mergeAt >= 0) return intelAbMerge(args2, mergeAt, out);
|
|
44529
44613
|
const workspaceArg = flag(args2, "--workspace");
|
|
44530
44614
|
const promptsArg = flag(args2, "--prompts");
|
|
44531
44615
|
const runnerId = flag(args2, "--runner") ?? "claude-code";
|