haansi 0.1.56 → 0.1.58
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/haansi.js +305 -45
- package/package.json +1 -1
package/dist/haansi.js
CHANGED
|
@@ -95,7 +95,7 @@ var require_package = __commonJS({
|
|
|
95
95
|
"package.json"(exports2, module2) {
|
|
96
96
|
module2.exports = {
|
|
97
97
|
name: "haansi",
|
|
98
|
-
version: "0.1.
|
|
98
|
+
version: "0.1.58",
|
|
99
99
|
description: "Haansi CLI - Session collector and MCP server for Claude Code & OpenAI Codex",
|
|
100
100
|
bin: {
|
|
101
101
|
haansi: "./dist/haansi.js"
|
|
@@ -19220,6 +19220,24 @@ var init_exclusions = __esm({
|
|
|
19220
19220
|
model: ["operational", "The model name the replay ran with."],
|
|
19221
19221
|
run_ref: ["not-personal", "A link to the customer's CI run."]
|
|
19222
19222
|
}),
|
|
19223
|
+
...exchangeExclusions(INTEL, "replay_sessions", {
|
|
19224
|
+
excluded_process_ids: [
|
|
19225
|
+
"join-key",
|
|
19226
|
+
"process_registry.process_id of the replayed workstream, matched by equality."
|
|
19227
|
+
],
|
|
19228
|
+
excluded_family_ids: [
|
|
19229
|
+
"join-key",
|
|
19230
|
+
"problem_families ids holding the replayed workstream, matched by equality."
|
|
19231
|
+
]
|
|
19232
|
+
}),
|
|
19233
|
+
...exchangeExclusions(INTEL, "replay_session_calls", {
|
|
19234
|
+
server: ["operational", "The MCP server's name, e.g. haansi-skills."],
|
|
19235
|
+
tool: ["operational", "The MCP tool's name, e.g. search_skills."],
|
|
19236
|
+
returned_ids: [
|
|
19237
|
+
"join-key",
|
|
19238
|
+
"Ids of the solutions, skills and revisions a tool returned."
|
|
19239
|
+
]
|
|
19240
|
+
}),
|
|
19223
19241
|
...exchangeExclusions(INTEL, "replay_task_results", {
|
|
19224
19242
|
inputs_fingerprint: [
|
|
19225
19243
|
"derived",
|
|
@@ -19228,7 +19246,15 @@ var init_exclusions = __esm({
|
|
|
19228
19246
|
arm: ["operational", "Closed CHECK vocabulary (baseline, haansi)."],
|
|
19229
19247
|
agent: ["operational", "The agent CLI's name, e.g. claude-code."],
|
|
19230
19248
|
model: ["operational", "The model name the replay ran with."],
|
|
19231
|
-
run_ref: ["not-personal", "A link to the customer's CI run."]
|
|
19249
|
+
run_ref: ["not-personal", "A link to the customer's CI run."],
|
|
19250
|
+
haansi_used: [
|
|
19251
|
+
"operational",
|
|
19252
|
+
"Closed CHECK vocabulary (used, partly, not_used)."
|
|
19253
|
+
],
|
|
19254
|
+
haansi_judge_tag: [
|
|
19255
|
+
"not-personal",
|
|
19256
|
+
"The prompt version that judged it (replay-use-judge-vN)."
|
|
19257
|
+
]
|
|
19232
19258
|
}),
|
|
19233
19259
|
...exchangeExclusions(INTEL, "replay_tasks", {
|
|
19234
19260
|
process_id: [
|
|
@@ -19315,7 +19341,11 @@ var init_exclusions = __esm({
|
|
|
19315
19341
|
"The CI's setup and test commands an org admin or CI owner typed, runtime versions, the runner image and the CLI version."
|
|
19316
19342
|
],
|
|
19317
19343
|
calibration_runs: ["operational", "Exit codes of the test runs."],
|
|
19318
|
-
calibration_run_ref: ["not-personal", "A link to the customer's CI run."]
|
|
19344
|
+
calibration_run_ref: ["not-personal", "A link to the customer's CI run."],
|
|
19345
|
+
review_state: [
|
|
19346
|
+
"operational",
|
|
19347
|
+
"Closed CHECK vocabulary (approved, excluded)."
|
|
19348
|
+
]
|
|
19319
19349
|
}),
|
|
19320
19350
|
...exchangeExclusions(INTEL, "problem_family_members", {
|
|
19321
19351
|
process_id: [
|
|
@@ -26583,6 +26613,12 @@ var init_table_registry = __esm({
|
|
|
26583
26613
|
entry("intelligence", INTELLIGENCE_DB, "replay_task_results", "direct", {
|
|
26584
26614
|
scopingColumn: "organization_id"
|
|
26585
26615
|
}),
|
|
26616
|
+
entry("intelligence", INTELLIGENCE_DB, "replay_sessions", "direct", {
|
|
26617
|
+
scopingColumn: "organization_id"
|
|
26618
|
+
}),
|
|
26619
|
+
entry("intelligence", INTELLIGENCE_DB, "replay_session_calls", "direct", {
|
|
26620
|
+
scopingColumn: "organization_id"
|
|
26621
|
+
}),
|
|
26586
26622
|
// A person's revert verdicts, the checks that validate a change and the
|
|
26587
26623
|
// replay commands, per repository (outcome evidence v2 plan).
|
|
26588
26624
|
entry("intelligence", INTELLIGENCE_DB, "revert_labels", "direct", {
|
|
@@ -77480,7 +77516,7 @@ var require_view = __commonJS({
|
|
|
77480
77516
|
var dirname5 = path2.dirname;
|
|
77481
77517
|
var basename4 = path2.basename;
|
|
77482
77518
|
var extname = path2.extname;
|
|
77483
|
-
var
|
|
77519
|
+
var join18 = path2.join;
|
|
77484
77520
|
var resolve5 = path2.resolve;
|
|
77485
77521
|
module2.exports = View;
|
|
77486
77522
|
function View(name, options) {
|
|
@@ -77542,12 +77578,12 @@ var require_view = __commonJS({
|
|
|
77542
77578
|
};
|
|
77543
77579
|
View.prototype.resolve = function resolve6(dir, file2) {
|
|
77544
77580
|
var ext = this.ext;
|
|
77545
|
-
var path3 =
|
|
77581
|
+
var path3 = join18(dir, file2);
|
|
77546
77582
|
var stat = tryStat(path3);
|
|
77547
77583
|
if (stat && stat.isFile()) {
|
|
77548
77584
|
return path3;
|
|
77549
77585
|
}
|
|
77550
|
-
path3 =
|
|
77586
|
+
path3 = join18(dir, basename4(file2, ext), "index" + ext);
|
|
77551
77587
|
stat = tryStat(path3);
|
|
77552
77588
|
if (stat && stat.isFile()) {
|
|
77553
77589
|
return path3;
|
|
@@ -81192,7 +81228,7 @@ var require_send = __commonJS({
|
|
|
81192
81228
|
var Stream = require("stream");
|
|
81193
81229
|
var util2 = require("util");
|
|
81194
81230
|
var extname = path2.extname;
|
|
81195
|
-
var
|
|
81231
|
+
var join18 = path2.join;
|
|
81196
81232
|
var normalize = path2.normalize;
|
|
81197
81233
|
var resolve5 = path2.resolve;
|
|
81198
81234
|
var sep2 = path2.sep;
|
|
@@ -81364,7 +81400,7 @@ var require_send = __commonJS({
|
|
|
81364
81400
|
return res;
|
|
81365
81401
|
}
|
|
81366
81402
|
parts = path3.split(sep2);
|
|
81367
|
-
path3 = normalize(
|
|
81403
|
+
path3 = normalize(join18(root, path3));
|
|
81368
81404
|
} else {
|
|
81369
81405
|
if (UP_PATH_REGEXP.test(path3)) {
|
|
81370
81406
|
debug('malicious path "%s"', path3);
|
|
@@ -81497,7 +81533,7 @@ var require_send = __commonJS({
|
|
|
81497
81533
|
if (err2) return self.onStatError(err2);
|
|
81498
81534
|
return self.error(404);
|
|
81499
81535
|
}
|
|
81500
|
-
var p =
|
|
81536
|
+
var p = join18(path3, self._index[i]);
|
|
81501
81537
|
debug('stat "%s"', p);
|
|
81502
81538
|
fs.stat(p, function(err3, stat) {
|
|
81503
81539
|
if (err3) return next(err3);
|
|
@@ -83604,7 +83640,7 @@ function resolveToken3() {
|
|
|
83604
83640
|
return null;
|
|
83605
83641
|
}
|
|
83606
83642
|
function triggerCollectInBackground() {
|
|
83607
|
-
if (isCollecting) return;
|
|
83643
|
+
if (isCollecting || REPLAY_SESSION) return;
|
|
83608
83644
|
isCollecting = true;
|
|
83609
83645
|
collect({}).then(() => {
|
|
83610
83646
|
lastCollectTime = Date.now();
|
|
@@ -83621,7 +83657,8 @@ function maybeCollect() {
|
|
|
83621
83657
|
async function apiGet2(path2, options) {
|
|
83622
83658
|
const headers = {
|
|
83623
83659
|
Authorization: `Bearer ${TOKEN}`,
|
|
83624
|
-
...options?.extraHeaders
|
|
83660
|
+
...options?.extraHeaders,
|
|
83661
|
+
...REPLAY_HEADERS
|
|
83625
83662
|
};
|
|
83626
83663
|
const response = await fetchWithTimeout(`${API_URL}/api/v1${path2}`, {
|
|
83627
83664
|
headers
|
|
@@ -83636,7 +83673,8 @@ async function apiPost(path2, body, options) {
|
|
|
83636
83673
|
const headers = {
|
|
83637
83674
|
Authorization: `Bearer ${TOKEN}`,
|
|
83638
83675
|
"Content-Type": "application/json",
|
|
83639
|
-
...options?.extraHeaders
|
|
83676
|
+
...options?.extraHeaders,
|
|
83677
|
+
...REPLAY_HEADERS
|
|
83640
83678
|
};
|
|
83641
83679
|
const response = await fetchWithTimeout(`${API_URL}/api/v1${path2}`, {
|
|
83642
83680
|
method: "POST",
|
|
@@ -83681,7 +83719,7 @@ async function main3() {
|
|
|
83681
83719
|
}
|
|
83682
83720
|
warmupInBackground();
|
|
83683
83721
|
}
|
|
83684
|
-
var import_dotenv3, import_path5, import_fs6, import_os5, DEFAULT_API_URL3, CREDENTIALS_FILE5, API_URL, AGENT_SOURCE, DEFAULT_SOURCE_TOOL, TOKEN, contextSchema, isCollecting, lastCollectTime, COLLECT_COOLDOWN_MS, mcpServer, server;
|
|
83722
|
+
var import_dotenv3, import_path5, import_fs6, import_os5, DEFAULT_API_URL3, CREDENTIALS_FILE5, API_URL, REPLAY_SESSION, REPLAY_HEADERS, AGENT_SOURCE, DEFAULT_SOURCE_TOOL, TOKEN, contextSchema, isCollecting, lastCollectTime, COLLECT_COOLDOWN_MS, mcpServer, server;
|
|
83685
83723
|
var init_mcp_server2 = __esm({
|
|
83686
83724
|
"../service-capture/agent-sessions/mcp-server.ts"() {
|
|
83687
83725
|
"use strict";
|
|
@@ -83700,6 +83738,10 @@ var init_mcp_server2 = __esm({
|
|
|
83700
83738
|
DEFAULT_API_URL3 = SERVICE_URL_DEFAULTS.haansiApiBaseUrl;
|
|
83701
83739
|
CREDENTIALS_FILE5 = (0, import_path5.join)((0, import_os5.homedir)(), ".haansi", "credentials.json");
|
|
83702
83740
|
API_URL = process.env.HAANSI_API_URL ?? DEFAULT_API_URL3;
|
|
83741
|
+
REPLAY_SESSION = /^[0-9a-f-]{36}$/i.test(
|
|
83742
|
+
process.env.HAANSI_REPLAY_SESSION?.trim() ?? ""
|
|
83743
|
+
) ? process.env.HAANSI_REPLAY_SESSION.trim() : null;
|
|
83744
|
+
REPLAY_HEADERS = REPLAY_SESSION ? { "X-Haansi-Replay-Session": REPLAY_SESSION } : {};
|
|
83703
83745
|
AGENT_SOURCE = process.env.HAANSI_AGENT_SOURCE?.toLowerCase();
|
|
83704
83746
|
DEFAULT_SOURCE_TOOL = AGENT_SOURCE === "codex" ? "codex-mcp" : "claude-code-mcp";
|
|
83705
83747
|
TOKEN = resolveToken3();
|
|
@@ -83920,6 +83962,13 @@ var init_mcp_server2 = __esm({
|
|
|
83920
83962
|
};
|
|
83921
83963
|
}
|
|
83922
83964
|
case "save_solution": {
|
|
83965
|
+
if (REPLAY_SESSION) {
|
|
83966
|
+
return {
|
|
83967
|
+
content: [
|
|
83968
|
+
{ type: "text", text: "Not saved: nothing is recorded here." }
|
|
83969
|
+
]
|
|
83970
|
+
};
|
|
83971
|
+
}
|
|
83923
83972
|
const {
|
|
83924
83973
|
problem_description,
|
|
83925
83974
|
solution_summary,
|
|
@@ -85562,16 +85611,125 @@ them all.`;
|
|
|
85562
85611
|
}
|
|
85563
85612
|
});
|
|
85564
85613
|
|
|
85614
|
+
// src/commands/replay-judge.ts
|
|
85615
|
+
function renderTemplate(template, vars) {
|
|
85616
|
+
return template.replace(
|
|
85617
|
+
/\{\{(\w+)\}\}/g,
|
|
85618
|
+
(all, key) => key in vars ? vars[key] : all
|
|
85619
|
+
);
|
|
85620
|
+
}
|
|
85621
|
+
async function changeOf(deps, dir, baseSha) {
|
|
85622
|
+
await deps.git(["add", "--all", "--intent-to-add"], dir);
|
|
85623
|
+
const diff = await deps.git(
|
|
85624
|
+
["diff", "--no-color", baseSha, "--", ".", ":(exclude).haansi-replay"],
|
|
85625
|
+
dir
|
|
85626
|
+
);
|
|
85627
|
+
return diff.code === 0 ? clip(diff.output, MAX_CHANGE_CHARS) : "";
|
|
85628
|
+
}
|
|
85629
|
+
function parseJudgement(text, returnedIds) {
|
|
85630
|
+
const match = /\{[\s\S]*\}/.exec(text);
|
|
85631
|
+
if (!match) return null;
|
|
85632
|
+
let v;
|
|
85633
|
+
try {
|
|
85634
|
+
v = JSON.parse(match[0]);
|
|
85635
|
+
} catch {
|
|
85636
|
+
return null;
|
|
85637
|
+
}
|
|
85638
|
+
if (!VERDICTS.includes(v.verdict)) return null;
|
|
85639
|
+
const items = Array.isArray(v.used_items) ? new Set(
|
|
85640
|
+
v.used_items.filter(
|
|
85641
|
+
(id) => typeof id === "string" && returnedIds.has(id)
|
|
85642
|
+
)
|
|
85643
|
+
) : /* @__PURE__ */ new Set();
|
|
85644
|
+
const reason = typeof v.reason === "string" && v.reason.trim() ? v.reason.trim().slice(0, MAX_REASON) : null;
|
|
85645
|
+
return { used: v.verdict, reason, usedItems: items.size };
|
|
85646
|
+
}
|
|
85647
|
+
async function judgeUse(deps, input) {
|
|
85648
|
+
const returnedIds = new Set(input.calls.flatMap((c) => c.returnedIds));
|
|
85649
|
+
if (input.calls.length === 0)
|
|
85650
|
+
return {
|
|
85651
|
+
used: "not_used",
|
|
85652
|
+
reason: "The agent did not call Haansi.",
|
|
85653
|
+
usedItems: 0,
|
|
85654
|
+
judgeTag: null
|
|
85655
|
+
};
|
|
85656
|
+
if (returnedIds.size === 0)
|
|
85657
|
+
return {
|
|
85658
|
+
used: "not_used",
|
|
85659
|
+
reason: "Haansi returned nothing.",
|
|
85660
|
+
usedItems: 0,
|
|
85661
|
+
judgeTag: null
|
|
85662
|
+
};
|
|
85663
|
+
if (!input.prompt)
|
|
85664
|
+
return { used: null, reason: null, usedItems: null, judgeTag: null };
|
|
85665
|
+
const tag = input.prompt.edited ? `${input.prompt.tag}+edited` : input.prompt.tag;
|
|
85666
|
+
const dir = (0, import_node_path10.join)(deps.workDir(), `${input.taskId}-judge`);
|
|
85667
|
+
const verdictFile = (0, import_node_path10.join)(dir, "verdict.json");
|
|
85668
|
+
const promptFile = (0, import_node_path10.join)(dir, "prompt.md");
|
|
85669
|
+
const configFile = (0, import_node_path10.join)(dir, "mcp.json");
|
|
85670
|
+
const returned = input.calls.map((c) => c.returnedText ?? "").filter(Boolean).join("\n\n");
|
|
85671
|
+
const prompt2 = [
|
|
85672
|
+
input.prompt.system,
|
|
85673
|
+
"",
|
|
85674
|
+
renderTemplate(input.prompt.user, {
|
|
85675
|
+
task: input.card,
|
|
85676
|
+
returned: clip(returned || "(only ids came back)", MAX_RETURNED_CHARS),
|
|
85677
|
+
change: input.change || "(no change)",
|
|
85678
|
+
verdict_file: verdictFile
|
|
85679
|
+
})
|
|
85680
|
+
].join("\n");
|
|
85681
|
+
try {
|
|
85682
|
+
deps.writeFile(promptFile, prompt2);
|
|
85683
|
+
deps.writeFile(configFile, JSON.stringify({ mcpServers: {} }));
|
|
85684
|
+
const ran = await deps.shell(
|
|
85685
|
+
input.agentCmd,
|
|
85686
|
+
dir,
|
|
85687
|
+
{
|
|
85688
|
+
HAANSI_REPLAY_PROMPT: prompt2,
|
|
85689
|
+
HAANSI_REPLAY_PROMPT_FILE: promptFile,
|
|
85690
|
+
HAANSI_REPLAY_MCP_CONFIG: configFile,
|
|
85691
|
+
HAANSI_REPLAY_VERDICT_FILE: verdictFile
|
|
85692
|
+
},
|
|
85693
|
+
input.timeoutMs
|
|
85694
|
+
);
|
|
85695
|
+
const judged = parseJudgement(deps.readFile(verdictFile) ?? "", returnedIds) ?? parseJudgement(ran.output, returnedIds);
|
|
85696
|
+
if (!judged) {
|
|
85697
|
+
deps.log(`\u2026 ${input.label} [haansi]: the judge gave no verdict`);
|
|
85698
|
+
return { used: null, reason: null, usedItems: null, judgeTag: tag };
|
|
85699
|
+
}
|
|
85700
|
+
return { ...judged, judgeTag: tag };
|
|
85701
|
+
} finally {
|
|
85702
|
+
deps.removeDir(dir);
|
|
85703
|
+
}
|
|
85704
|
+
}
|
|
85705
|
+
var import_node_path10, MAX_CHANGE_CHARS, MAX_RETURNED_CHARS, MAX_REASON, VERDICTS, clip;
|
|
85706
|
+
var init_replay_judge = __esm({
|
|
85707
|
+
"src/commands/replay-judge.ts"() {
|
|
85708
|
+
"use strict";
|
|
85709
|
+
import_node_path10 = require("node:path");
|
|
85710
|
+
MAX_CHANGE_CHARS = 6e4;
|
|
85711
|
+
MAX_RETURNED_CHARS = 6e4;
|
|
85712
|
+
MAX_REASON = 300;
|
|
85713
|
+
VERDICTS = ["used", "partly", "not_used"];
|
|
85714
|
+
clip = (text, max2) => text.length > max2 ? `${text.slice(0, max2)}
|
|
85715
|
+
\u2026 (trimmed)` : text;
|
|
85716
|
+
}
|
|
85717
|
+
});
|
|
85718
|
+
|
|
85565
85719
|
// src/commands/replay-tasks.ts
|
|
85566
85720
|
var replay_tasks_exports = {};
|
|
85567
85721
|
__export(replay_tasks_exports, {
|
|
85722
|
+
HAANSI_LINE: () => HAANSI_LINE,
|
|
85568
85723
|
areaOf: () => areaOf,
|
|
85569
85724
|
cardPrompt: () => cardPrompt,
|
|
85570
85725
|
changedFiles: () => changedFiles,
|
|
85726
|
+
haansiText: () => haansiText,
|
|
85727
|
+
haansiUseOf: () => haansiUseOf,
|
|
85728
|
+
mcpConfig: () => mcpConfig,
|
|
85571
85729
|
overlapOf: () => overlapOf,
|
|
85572
85730
|
runStoredReplays: () => runStoredReplays
|
|
85573
85731
|
});
|
|
85574
|
-
function cardPrompt(task, feedback) {
|
|
85732
|
+
function cardPrompt(task, feedback, arm = "baseline") {
|
|
85575
85733
|
return [
|
|
85576
85734
|
"# Task",
|
|
85577
85735
|
"",
|
|
@@ -85582,10 +85740,40 @@ function cardPrompt(task, feedback) {
|
|
|
85582
85740
|
task.card.acceptance,
|
|
85583
85741
|
...task.card.area ? ["", "## Part of the code", "", task.card.area] : [],
|
|
85584
85742
|
"",
|
|
85743
|
+
...arm === "haansi" ? [HAANSI_LINE, ""] : [],
|
|
85585
85744
|
...feedback ? [FEEDBACK_HEADER, "", "```", feedback, "```", ""] : [],
|
|
85586
85745
|
"Work in this repository as it is now. Do not look at later commits or other branches."
|
|
85587
85746
|
].join("\n");
|
|
85588
85747
|
}
|
|
85748
|
+
function mcpConfig(arm, session) {
|
|
85749
|
+
if (arm !== "haansi" || !session) return JSON.stringify({ mcpServers: {} });
|
|
85750
|
+
const base = session.apiUrl.replace(/\/+$/, "");
|
|
85751
|
+
return JSON.stringify(
|
|
85752
|
+
{
|
|
85753
|
+
mcpServers: {
|
|
85754
|
+
"haansi-solutions": {
|
|
85755
|
+
command: "haansi",
|
|
85756
|
+
args: ["mcp-server"],
|
|
85757
|
+
env: {
|
|
85758
|
+
HAANSI_API_URL: base,
|
|
85759
|
+
HAANSI_TOKEN: session.token,
|
|
85760
|
+
HAANSI_REPLAY_SESSION: session.sessionId
|
|
85761
|
+
}
|
|
85762
|
+
},
|
|
85763
|
+
"haansi-skills": {
|
|
85764
|
+
type: "http",
|
|
85765
|
+
url: `${base}/api/v1/memory/skills/mcp`,
|
|
85766
|
+
headers: {
|
|
85767
|
+
Authorization: `Bearer ${session.token}`,
|
|
85768
|
+
"X-Haansi-Replay-Session": session.sessionId
|
|
85769
|
+
}
|
|
85770
|
+
}
|
|
85771
|
+
}
|
|
85772
|
+
},
|
|
85773
|
+
null,
|
|
85774
|
+
2
|
|
85775
|
+
);
|
|
85776
|
+
}
|
|
85589
85777
|
function areaOf(path2) {
|
|
85590
85778
|
const dirs = path2.split("/").slice(0, -1);
|
|
85591
85779
|
return dirs.length ? dirs.slice(0, 2).join("/") : ".";
|
|
@@ -85644,14 +85832,14 @@ async function runStoredReplays(options, deps) {
|
|
|
85644
85832
|
(t) => states.get(t.taskId) === "valid" && t.testFiles.length > 0 && t.testFiles.every(safeTestFile) && SHA.test(t.baseSha) && SHA.test(t.targetSha)
|
|
85645
85833
|
);
|
|
85646
85834
|
const notValid = set2.tasks.length - valid.length;
|
|
85647
|
-
|
|
85648
|
-
if (arms.includes("haansi")) {
|
|
85649
|
-
deps.log(
|
|
85650
|
-
"The with-Haansi arm on stored tasks needs Haansi answering as of each task's start; running the baseline arm only."
|
|
85651
|
-
);
|
|
85652
|
-
arms = arms.filter((a) => a !== "haansi");
|
|
85653
|
-
}
|
|
85835
|
+
const arms = options.arms;
|
|
85654
85836
|
const chosen = options.limit ? valid.slice(0, options.limit) : valid;
|
|
85837
|
+
let judgePrompt;
|
|
85838
|
+
const promptForJudge = async () => {
|
|
85839
|
+
if (judgePrompt === void 0)
|
|
85840
|
+
judgePrompt = await deps.getJudgePrompt().catch(() => null);
|
|
85841
|
+
return judgePrompt;
|
|
85842
|
+
};
|
|
85655
85843
|
const results = [];
|
|
85656
85844
|
for (const task of chosen) {
|
|
85657
85845
|
const label = task.name ?? task.taskKey;
|
|
@@ -85669,18 +85857,47 @@ async function runStoredReplays(options, deps) {
|
|
|
85669
85857
|
};
|
|
85670
85858
|
for (const arm of arms) {
|
|
85671
85859
|
let changed = [];
|
|
85860
|
+
let change = "";
|
|
85672
85861
|
try {
|
|
85862
|
+
const session = arm === "haansi" ? {
|
|
85863
|
+
...await deps.openSession(task.taskId),
|
|
85864
|
+
apiUrl: deps.apiUrl,
|
|
85865
|
+
token: deps.token
|
|
85866
|
+
} : null;
|
|
85867
|
+
const configPath = `${deps.workDir()}/${task.taskId}-${arm}.mcp.json`;
|
|
85868
|
+
deps.writeFile(configPath, mcpConfig(arm, session));
|
|
85673
85869
|
const r = await replayArm(
|
|
85674
85870
|
deps,
|
|
85675
85871
|
{ ...options, setupCmd, testCmd },
|
|
85676
85872
|
asTask,
|
|
85677
85873
|
arm,
|
|
85678
85874
|
null,
|
|
85679
|
-
(feedback) => cardPrompt(task, feedback),
|
|
85875
|
+
(feedback) => cardPrompt(task, feedback, arm),
|
|
85680
85876
|
async (dir) => {
|
|
85681
85877
|
changed = await changedFiles(deps, dir, task.baseSha);
|
|
85682
|
-
|
|
85878
|
+
if (arm === "haansi")
|
|
85879
|
+
change = await changeOf(deps, dir, task.baseSha);
|
|
85880
|
+
},
|
|
85881
|
+
{ HAANSI_REPLAY_MCP_CONFIG: configPath }
|
|
85683
85882
|
);
|
|
85883
|
+
deps.removeFile(configPath);
|
|
85884
|
+
let haansi = null;
|
|
85885
|
+
if (session) {
|
|
85886
|
+
const { calls } = await deps.getCalls(session.sessionId);
|
|
85887
|
+
haansi = {
|
|
85888
|
+
...haansiUseOf(calls),
|
|
85889
|
+
...await judgeUse(deps, {
|
|
85890
|
+
label,
|
|
85891
|
+
taskId: task.taskId,
|
|
85892
|
+
agentCmd: options.agentCmd,
|
|
85893
|
+
timeoutMs: options.timeoutMs,
|
|
85894
|
+
card: cardPrompt(task, null),
|
|
85895
|
+
calls,
|
|
85896
|
+
change,
|
|
85897
|
+
prompt: await promptForJudge()
|
|
85898
|
+
})
|
|
85899
|
+
};
|
|
85900
|
+
}
|
|
85684
85901
|
const overlap = overlapOf(changed, task);
|
|
85685
85902
|
results.push({
|
|
85686
85903
|
taskId: task.taskId,
|
|
@@ -85691,10 +85908,11 @@ async function runStoredReplays(options, deps) {
|
|
|
85691
85908
|
attempts: r.attempts,
|
|
85692
85909
|
maxAttempts: r.maxAttempts,
|
|
85693
85910
|
durationMs: r.durationMs,
|
|
85694
|
-
overlap
|
|
85911
|
+
overlap,
|
|
85912
|
+
haansi
|
|
85695
85913
|
});
|
|
85696
85914
|
deps.log(
|
|
85697
|
-
`${r.passed ? "pass" : "fail"} ${arm.padEnd(8)} ${label} (${r.attempts}/${r.maxAttempts} attempts; files ${pct(overlap.fileRecall)} of the target's, ${pct(overlap.filePrecision)} of its own on target)`
|
|
85915
|
+
`${r.passed ? "pass" : "fail"} ${arm.padEnd(8)} ${label} (${r.attempts}/${r.maxAttempts} attempts; files ${pct(overlap.fileRecall)} of the target's, ${pct(overlap.filePrecision)} of its own on target${haansi ? `; ${haansiText(haansi)}` : ""})`
|
|
85698
85916
|
);
|
|
85699
85917
|
} catch (err2) {
|
|
85700
85918
|
deps.log(
|
|
@@ -85721,14 +85939,28 @@ async function runStoredReplays(options, deps) {
|
|
|
85721
85939
|
}
|
|
85722
85940
|
return { results, notValid, recorded };
|
|
85723
85941
|
}
|
|
85724
|
-
|
|
85942
|
+
function haansiUseOf(calls) {
|
|
85943
|
+
return {
|
|
85944
|
+
calls: calls.length,
|
|
85945
|
+
returned: calls.reduce((n, c) => n + c.returnedIds.length, 0)
|
|
85946
|
+
};
|
|
85947
|
+
}
|
|
85948
|
+
function haansiText(use) {
|
|
85949
|
+
if (use.calls === 0) return "did not call Haansi";
|
|
85950
|
+
const base = `called Haansi ${use.calls}\xD7, ${use.returned} item${use.returned === 1 ? "" : "s"} returned`;
|
|
85951
|
+
if (!use.used) return base;
|
|
85952
|
+
return use.used === "not_used" ? `${base}, not used` : `${base}, ${use.used === "partly" ? "partly " : ""}used${use.usedItems ? ` (${use.usedItems})` : ""}`;
|
|
85953
|
+
}
|
|
85954
|
+
var FEEDBACK_HEADER, REPORT_CHUNK2, HAANSI_LINE, share, pct;
|
|
85725
85955
|
var init_replay_tasks = __esm({
|
|
85726
85956
|
"src/commands/replay-tasks.ts"() {
|
|
85727
85957
|
"use strict";
|
|
85728
85958
|
init_replay_calibrate();
|
|
85959
|
+
init_replay_judge();
|
|
85729
85960
|
init_replay();
|
|
85730
85961
|
FEEDBACK_HEADER = "Your previous attempt did not pass the tests. Their output ends with:";
|
|
85731
85962
|
REPORT_CHUNK2 = 200;
|
|
85963
|
+
HAANSI_LINE = "Haansi's MCP servers are available to you: they give access to your organization's knowledge (solutions to past problems, team skills, digests, guides and Exchange). Use them if they help.";
|
|
85732
85964
|
share = (part, whole) => whole === 0 ? null : Math.round(part / whole * 1e3) / 1e3;
|
|
85733
85965
|
pct = (v) => v === null ? "n/a" : `${Math.round(v * 100)}%`;
|
|
85734
85966
|
}
|
|
@@ -85902,9 +86134,9 @@ async function cleanClone(deps, dir, sha, unreachable) {
|
|
|
85902
86134
|
function cleanCheckout(deps, dir, task) {
|
|
85903
86135
|
return cleanClone(deps, dir, task.baseSha, task.targetSha);
|
|
85904
86136
|
}
|
|
85905
|
-
async function replayArm(deps, options, task, arm, playbook, promptOf = (feedback) => replayPrompt(task, arm, feedback), inspect) {
|
|
86137
|
+
async function replayArm(deps, options, task, arm, playbook, promptOf = (feedback) => replayPrompt(task, arm, feedback), inspect, agentEnv = {}) {
|
|
85906
86138
|
const started = deps.now();
|
|
85907
|
-
const dir = (0,
|
|
86139
|
+
const dir = (0, import_node_path11.join)(
|
|
85908
86140
|
deps.workDir(),
|
|
85909
86141
|
`${task.processId.replace(/[^a-zA-Z0-9_-]/g, "")}-${arm}`
|
|
85910
86142
|
);
|
|
@@ -85930,10 +86162,15 @@ async function replayArm(deps, options, task, arm, playbook, promptOf = (feedbac
|
|
|
85930
86162
|
}
|
|
85931
86163
|
deps.log(`\u2026 ${label}: set up in ${since(at)}`);
|
|
85932
86164
|
}
|
|
85933
|
-
const promptFile = (0,
|
|
86165
|
+
const promptFile = (0, import_node_path11.join)(dir, ".haansi-replay", "prompt.md");
|
|
86166
|
+
let mcpConfigFile = agentEnv.HAANSI_REPLAY_MCP_CONFIG;
|
|
86167
|
+
if (!mcpConfigFile) {
|
|
86168
|
+
mcpConfigFile = `${dir}.mcp.json`;
|
|
86169
|
+
deps.writeFile(mcpConfigFile, JSON.stringify({ mcpServers: {} }));
|
|
86170
|
+
}
|
|
85934
86171
|
if (arm === "haansi" && playbook)
|
|
85935
86172
|
deps.writeFile(
|
|
85936
|
-
(0,
|
|
86173
|
+
(0, import_node_path11.join)(dir, ".haansi-replay", "playbook.md"),
|
|
85937
86174
|
playbook.skillMd
|
|
85938
86175
|
);
|
|
85939
86176
|
let feedback = null;
|
|
@@ -85947,14 +86184,19 @@ async function replayArm(deps, options, task, arm, playbook, promptOf = (feedbac
|
|
|
85947
86184
|
await deps.shell(
|
|
85948
86185
|
options.agentCmd,
|
|
85949
86186
|
dir,
|
|
85950
|
-
{
|
|
86187
|
+
{
|
|
86188
|
+
...agentEnv,
|
|
86189
|
+
HAANSI_REPLAY_MCP_CONFIG: mcpConfigFile,
|
|
86190
|
+
HAANSI_REPLAY_PROMPT: prompt2,
|
|
86191
|
+
HAANSI_REPLAY_PROMPT_FILE: promptFile
|
|
86192
|
+
},
|
|
85951
86193
|
options.timeoutMs
|
|
85952
86194
|
);
|
|
85953
|
-
const own = new Map(files.map((f) => [f, deps.readFile((0,
|
|
86195
|
+
const own = new Map(files.map((f) => [f, deps.readFile((0, import_node_path11.join)(dir, f))]));
|
|
85954
86196
|
for (const f of files) {
|
|
85955
86197
|
const content = await deps.showFile(task.targetSha, f);
|
|
85956
|
-
if (content === null) deps.removeFile((0,
|
|
85957
|
-
else deps.writeFile((0,
|
|
86198
|
+
if (content === null) deps.removeFile((0, import_node_path11.join)(dir, f));
|
|
86199
|
+
else deps.writeFile((0, import_node_path11.join)(dir, f), content);
|
|
85958
86200
|
}
|
|
85959
86201
|
const testAt = deps.now();
|
|
85960
86202
|
deps.log(`\u2026 ${label}: agent done in ${since(agentAt)}, grading`);
|
|
@@ -85984,8 +86226,8 @@ async function replayArm(deps, options, task, arm, playbook, promptOf = (feedbac
|
|
|
85984
86226
|
};
|
|
85985
86227
|
}
|
|
85986
86228
|
for (const [file2, content] of own) {
|
|
85987
|
-
if (content === null) deps.removeFile((0,
|
|
85988
|
-
else deps.writeFile((0,
|
|
86229
|
+
if (content === null) deps.removeFile((0, import_node_path11.join)(dir, file2));
|
|
86230
|
+
else deps.writeFile((0, import_node_path11.join)(dir, file2), content);
|
|
85989
86231
|
}
|
|
85990
86232
|
feedback = test.output.slice(-FEEDBACK_CHARS);
|
|
85991
86233
|
}
|
|
@@ -86001,6 +86243,7 @@ async function replayArm(deps, options, task, arm, playbook, promptOf = (feedbac
|
|
|
86001
86243
|
};
|
|
86002
86244
|
} finally {
|
|
86003
86245
|
deps.removeDir(dir);
|
|
86246
|
+
if (!agentEnv.HAANSI_REPLAY_MCP_CONFIG) deps.removeFile(`${dir}.mcp.json`);
|
|
86004
86247
|
}
|
|
86005
86248
|
}
|
|
86006
86249
|
async function runReplays(options, deps) {
|
|
@@ -86134,7 +86377,7 @@ function localDeps(work) {
|
|
|
86134
86377
|
repoDir: process.cwd(),
|
|
86135
86378
|
workDir: () => work,
|
|
86136
86379
|
writeFile: (path2, content) => {
|
|
86137
|
-
(0, import_node_fs11.mkdirSync)((0,
|
|
86380
|
+
(0, import_node_fs11.mkdirSync)((0, import_node_path11.dirname)(path2), { recursive: true });
|
|
86138
86381
|
(0, import_node_fs11.writeFileSync)(path2, content);
|
|
86139
86382
|
},
|
|
86140
86383
|
readFile: (path2) => (0, import_node_fs11.existsSync)(path2) ? (0, import_node_fs11.readFileSync)(path2, "utf-8") : null,
|
|
@@ -86163,7 +86406,7 @@ async function replay(argv) {
|
|
|
86163
86406
|
await replayStored(options, token, apiUrl2);
|
|
86164
86407
|
return;
|
|
86165
86408
|
}
|
|
86166
|
-
const work = (0, import_node_fs11.mkdtempSync)((0,
|
|
86409
|
+
const work = (0, import_node_fs11.mkdtempSync)((0, import_node_path11.join)((0, import_node_os10.tmpdir)(), "haansi-replay-"));
|
|
86167
86410
|
try {
|
|
86168
86411
|
const { results, skipped, recorded } = await runReplays(options, {
|
|
86169
86412
|
getTasks: async (familyId, cutoff) => (await apiGet3(
|
|
@@ -86200,7 +86443,7 @@ Baseline: ${passed("baseline")}/${ran("baseline")} passed \xB7 With Haansi: ${pa
|
|
|
86200
86443
|
async function replayStored(options, token, apiUrl2) {
|
|
86201
86444
|
const { runStoredReplays: runStoredReplays2 } = await Promise.resolve().then(() => (init_replay_tasks(), replay_tasks_exports));
|
|
86202
86445
|
const { cliVersion: cliVersion2 } = await Promise.resolve().then(() => (init_replay_calibrate(), replay_calibrate_exports));
|
|
86203
|
-
const work = (0, import_node_fs11.mkdtempSync)((0,
|
|
86446
|
+
const work = (0, import_node_fs11.mkdtempSync)((0, import_node_path11.join)((0, import_node_os10.tmpdir)(), "haansi-replay-"));
|
|
86204
86447
|
try {
|
|
86205
86448
|
const { results, notValid, recorded } = await runStoredReplays2(options, {
|
|
86206
86449
|
...localDeps(work),
|
|
@@ -86222,12 +86465,29 @@ async function replayStored(options, token, apiUrl2) {
|
|
|
86222
86465
|
apiUrl2
|
|
86223
86466
|
),
|
|
86224
86467
|
env: process.env,
|
|
86225
|
-
cliVersion: cliVersion2()
|
|
86468
|
+
cliVersion: cliVersion2(),
|
|
86469
|
+
openSession: async (taskId) => await apiPost2(
|
|
86470
|
+
`/memory/replays/tasks/${encodeURIComponent(taskId)}/sessions`,
|
|
86471
|
+
{},
|
|
86472
|
+
token,
|
|
86473
|
+
apiUrl2
|
|
86474
|
+
),
|
|
86475
|
+
getCalls: (sessionId) => apiGet3(
|
|
86476
|
+
`/memory/replays/sessions/${encodeURIComponent(sessionId)}/calls?include=text`,
|
|
86477
|
+
token,
|
|
86478
|
+
apiUrl2
|
|
86479
|
+
),
|
|
86480
|
+
getJudgePrompt: () => apiGet3("/memory/replays/prompts/use-judge", token, apiUrl2),
|
|
86481
|
+
apiUrl: apiUrl2,
|
|
86482
|
+
token
|
|
86226
86483
|
});
|
|
86227
|
-
const
|
|
86484
|
+
const arm = (a) => {
|
|
86485
|
+
const mine = results.filter((r) => r.arm === a);
|
|
86486
|
+
return `${mine.filter((r) => r.passed).length}/${mine.length}`;
|
|
86487
|
+
};
|
|
86228
86488
|
console.log(
|
|
86229
86489
|
`
|
|
86230
|
-
Baseline: ${passed
|
|
86490
|
+
Baseline: ${arm("baseline")} passed \xB7 With Haansi: ${arm("haansi")} passed \xB7 ${notValid} task(s) not valid to replay`
|
|
86231
86491
|
);
|
|
86232
86492
|
console.log(
|
|
86233
86493
|
recorded === null ? options.dryRun ? "Dry run: nothing reported." : "Nothing to report." : `Reported ${recorded} result(s) to Haansi.`
|
|
@@ -86239,14 +86499,14 @@ Baseline: ${passed}/${results.length} passed \xB7 ${notValid} task(s) not valid
|
|
|
86239
86499
|
(0, import_node_fs11.rmSync)(work, { recursive: true, force: true });
|
|
86240
86500
|
}
|
|
86241
86501
|
}
|
|
86242
|
-
var import_node_child_process4, import_node_fs11, import_node_os10,
|
|
86502
|
+
var import_node_child_process4, import_node_fs11, import_node_os10, import_node_path11, SHA, ARMS, FEEDBACK_CHARS, FAILURE_LINES2, REPLAY_BRANCH, USAGE2;
|
|
86243
86503
|
var init_replay = __esm({
|
|
86244
86504
|
"src/commands/replay.ts"() {
|
|
86245
86505
|
"use strict";
|
|
86246
86506
|
import_node_child_process4 = require("node:child_process");
|
|
86247
86507
|
import_node_fs11 = require("node:fs");
|
|
86248
86508
|
import_node_os10 = require("node:os");
|
|
86249
|
-
|
|
86509
|
+
import_node_path11 = require("node:path");
|
|
86250
86510
|
init_paths();
|
|
86251
86511
|
init_api2();
|
|
86252
86512
|
SHA = /^[0-9a-f]{7,40}$/i;
|
|
@@ -86272,7 +86532,7 @@ repository in Haansi (Settings \u2192 Data sources) for any not given here.
|
|
|
86272
86532
|
|
|
86273
86533
|
Run from a clone of the repository. Example (GitHub Actions):
|
|
86274
86534
|
haansi replay run --family "$FAMILY_ID" --setup-cmd 'npm ci' \\
|
|
86275
|
-
--agent-cmd 'claude -p "$HAANSI_REPLAY_PROMPT" --permission-mode acceptEdits' \\
|
|
86535
|
+
--agent-cmd 'claude -p "$HAANSI_REPLAY_PROMPT" --permission-mode acceptEdits --mcp-config "$HAANSI_REPLAY_MCP_CONFIG"' \\
|
|
86276
86536
|
--test-cmd 'npx vitest run $HAANSI_REPLAY_TEST_FILES' \\
|
|
86277
86537
|
--agent-name claude-code --model claude-sonnet-5-5`;
|
|
86278
86538
|
}
|