@visual-z/xeno 0.7.2 → 0.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +60 -0
- package/README.md +36 -9
- package/docs/foundations.md +1 -1
- package/package.json +3 -1
- package/src/apps/cli.ts +4 -3
- package/src/apps/commands.ts +8 -0
- package/src/apps/goal-flow.ts +70 -0
- package/src/apps/render.ts +0 -2
- package/src/apps/session-hub.ts +6 -5
- package/src/apps/session-lifecycle.ts +64 -0
- package/src/apps/shell.ts +89 -34
- package/src/apps/startup.ts +32 -31
- package/src/apps/ui/app.ts +105 -13
- package/src/apps/ui/control-model.ts +18 -1
- package/src/apps/ui/surface.ts +2 -0
- package/src/apps/update-check.ts +1 -1
- package/src/core/acceptance.ts +41 -9
- package/src/core/blind-checks.ts +72 -0
- package/src/core/branch-receipt.ts +3 -1
- package/src/core/checkpoints.ts +21 -0
- package/src/core/churn.ts +2 -2
- package/src/core/completion-review.ts +18 -11
- package/src/core/continuation.ts +11 -10
- package/src/core/control-view.ts +5 -8
- package/src/core/deadline.ts +2 -1
- package/src/core/derive.ts +0 -7
- package/src/core/environment.ts +2 -2
- package/src/core/event-validation.ts +19 -6
- package/src/core/events.ts +41 -32
- package/src/core/evidence-ladder.ts +62 -0
- package/src/core/evidence-links.ts +0 -10
- package/src/core/evidence.ts +1 -2
- package/src/core/export.ts +6 -11
- package/src/core/goal-view.ts +64 -0
- package/src/core/goal.ts +200 -0
- package/src/core/history.ts +1 -11
- package/src/core/json-event.ts +0 -6
- package/src/core/outcome.ts +0 -1
- package/src/core/program-status.ts +11 -2
- package/src/core/progress.ts +14 -8
- package/src/core/project.ts +6 -16
- package/src/core/read-bounds.ts +11 -2
- package/src/core/read-observation.ts +24 -0
- package/src/core/request-budget.ts +13 -3
- package/src/core/request-state.ts +178 -19
- package/src/core/response-recovery.ts +15 -2
- package/src/core/session-entry.ts +8 -3
- package/src/core/session-exit.ts +19 -3
- package/src/core/session-format.ts +169 -0
- package/src/core/settings.ts +7 -27
- package/src/core/stall.ts +30 -7
- package/src/core/stop-report.ts +4 -3
- package/src/core/transcript.ts +22 -2
- package/src/runtime/blind-check.ts +36 -0
- package/src/runtime/counter-check.ts +74 -0
- package/src/runtime/exec-sessions.ts +11 -0
- package/src/runtime/foundation.ts +46 -8
- package/src/runtime/loop.ts +239 -430
- package/src/runtime/oauth.ts +1 -3
- package/src/runtime/openai.ts +2 -2
- package/src/runtime/pi-provider.ts +3 -3
- package/src/runtime/provider-request.ts +2 -1
- package/src/runtime/provider.ts +33 -3
- package/src/runtime/retry.ts +9 -2
- package/src/runtime/runtime-target.ts +2 -2
- package/src/runtime/session-control.ts +4 -4
- package/src/runtime/tools.ts +44 -42
- package/src/runtime/usage.ts +28 -0
- package/src/runtime/version.ts +12 -0
- package/src/runtime/workspace-lifecycle.ts +31 -0
- package/src/store/blind-checks.ts +32 -0
- package/src/store/checkpoint-history.ts +1 -12
- package/src/store/file-index.ts +88 -7
- package/src/store/log.ts +25 -27
- package/src/store/recovery.ts +1 -3
- package/src/store/replay.ts +2 -2
- package/src/store/restore.ts +10 -202
- package/src/store/search.ts +7 -5
- package/src/store/session-archive.ts +0 -1
- package/src/store/sessions.ts +31 -33
- package/src/store/workspace-clone.ts +42 -0
- package/src/store/workspace.ts +10 -157
- package/src/tui/ui/duration.ts +16 -0
- package/src/tui/ui/layout.ts +4 -4
- package/src/tui/ui/screen.ts +13 -7
- package/src/tui/ui/status.ts +99 -17
- package/src/core/benchmark.ts +0 -195
- package/src/core/evaluation-coverage.ts +0 -74
- package/src/core/experiment.ts +0 -173
- package/src/core/harness-comparison.ts +0 -90
- package/src/core/harness-diagnostics.ts +0 -206
- package/src/core/native-eval.ts +0 -25
- package/src/core/policy-evaluation.ts +0 -55
- package/src/core/query-eval-contract.ts +0 -14
- package/src/core/recall-analysis.ts +0 -90
- package/src/core/recall-trial.ts +0 -153
- package/src/core/recovery-corpus.ts +0 -17
- package/src/core/recovery-evaluation.ts +0 -60
- package/src/core/tool-mode-protocol.ts +0 -25
- package/src/core/trial-lifecycle.ts +0 -14
- package/src/core/window-trial.ts +0 -65
- package/src/index.ts +0 -10
- package/src/runtime/benchmark.ts +0 -187
- package/src/runtime/harness-comparison.ts +0 -376
- package/src/runtime/harness-eval.ts +0 -462
- package/src/runtime/native-eval-fixtures.ts +0 -33
- package/src/runtime/native-eval.ts +0 -146
- package/src/runtime/query-eval-verifier.ts +0 -28
- package/src/runtime/recall-trial.ts +0 -226
- package/src/runtime/recovery-coverage.ts +0 -71
- package/src/runtime/recovery-evidence.ts +0 -76
- package/src/runtime/recovery-fixtures.ts +0 -116
- package/src/runtime/tool-mode-eval.ts +0 -146
- package/src/runtime/trial-control.ts +0 -45
- package/src/runtime/window-trial.ts +0 -141
- package/src/store/git-branch.ts +0 -35
- package/src/tui/ui/testing.ts +0 -5
|
@@ -1,116 +0,0 @@
|
|
|
1
|
-
/** R12c: sanitized, executable analogues of the audit's failure mechanisms. */
|
|
2
|
-
import {createHash} from "node:crypto";
|
|
3
|
-
import {seededRandom} from "../core/experiment.ts";
|
|
4
|
-
import {recoveryCase,RECOVERY_TASKS,RECOVERY_SOURCE_REPORT,type RecoveryTask} from "../core/recovery-corpus.ts";
|
|
5
|
-
import type {AcceptanceContract} from "../core/acceptance.ts";
|
|
6
|
-
import type {EvalFixture,EvalPlan} from "./harness-eval.ts";
|
|
7
|
-
import type {ComparisonBackend} from "./harness-comparison.ts";
|
|
8
|
-
|
|
9
|
-
const digest=(text:string)=>createHash("sha256").update(text).digest("hex");
|
|
10
|
-
export const RECOVERY_SYSTEM="Solve the supplied recovery task using actual file and tool evidence. Preserve the fixture and verifier inputs. " +
|
|
11
|
-
"Inspect failed-call premises before retrying. A clipped preview is not the complete source. Recover closed process evidence without replaying its effects. " +
|
|
12
|
-
"Verify the requested artifacts with bun run gate. Building, observing a running target, and the user's acceptance are separate facts. " +
|
|
13
|
-
"Report unavailable capabilities or pending human review honestly. Work within this session without delegation.";
|
|
14
|
-
export function recoveryFixture(seed:number,task:RecoveryTask,repeat:number):EvalFixture{
|
|
15
|
-
const specification=recoveryCase(task)!,random=seededRandom(seed+RECOVERY_TASKS.indexOf(task)*1000+repeat);
|
|
16
|
-
const answer=Array.from({length:4},()=>Math.floor(random()*0x100000000).toString(16).padStart(8,"0")).join("")+"\n";
|
|
17
|
-
const files:Record<string,string>={"source.txt":`CODE=${answer}`,"package.json":JSON.stringify({scripts:{gate:"bun verify.ts",build:"bun build.ts"}})+"\n"};
|
|
18
|
-
const goals:Record<string,string>={"answer.txt":digest(answer)},mutable=["answer.txt"];
|
|
19
|
-
let scenario="Copy the CODE value from source.txt to answer.txt with one trailing newline. ";
|
|
20
|
-
if(task==="recovery-missing-path"){
|
|
21
|
-
files["answer.txt"]="pending\n";scenario+="Replace pending in answer.txt; an earlier editor request omitted path. Use the actual file identity. ";
|
|
22
|
-
}else if(task==="recovery-stale-version"){
|
|
23
|
-
files["draft.txt"]="before\nexternal change\n";mutable.push("draft.txt");goals["draft.txt"]=digest("after\nexternal change\n");
|
|
24
|
-
scenario+=`Change before to after in draft.txt while retaining its other line. An old observation had sha256 ${digest("before\n")}; it may be stale. `;
|
|
25
|
-
}else if(task==="recovery-long-output"){
|
|
26
|
-
files["source.txt"]="x".repeat(5000)+`CODE=${answer.trim()}`+"y".repeat(10000)+"\n";
|
|
27
|
-
scenario="Recover the 32 hexadecimal characters after CODE= in source.txt and put them in answer.txt with one trailing newline. The line exceeds a preview; use immutable original evidence when needed. ";
|
|
28
|
-
}else if(task==="recovery-api-mismatch"){
|
|
29
|
-
files["compat.ts"]='// Portable legacy API fixture; the optional filter flag is unsupported.\n'+
|
|
30
|
-
'if(Bun.argv.some(arg=>arg.startsWith("--filter")))throw new TypeError("extract() does not support the filter option");\n'+
|
|
31
|
-
'const source=await Bun.file("source.txt").text();await Bun.write("answer.txt",source.slice(5));\n';
|
|
32
|
-
scenario+="The local compat.ts implements a legacy API; a suggested invocation was bun compat.ts --filter=safe. Inspect what this installed implementation supports before using it. ";
|
|
33
|
-
}else if(task==="recovery-closed-process"){
|
|
34
|
-
files["producer.ts"]='const count=Number(await Bun.file("counter.txt").text().catch(()=>"0"));await Bun.write("counter.txt",String(count+1));console.log((await Bun.file("source.txt").text()).trim());\n';
|
|
35
|
-
goals["counter.txt"]=digest("1");mutable.push("counter.txt");
|
|
36
|
-
scenario="Run bun producer.ts exactly once to obtain CODE. It increments a counter and exits. Recover its terminal receipt if further process input fails; do not launch it again. Put CODE in answer.txt with one newline. ";
|
|
37
|
-
}else if(task==="recovery-unchanged-retry"){
|
|
38
|
-
files["draft.txt"]="present\n";mutable.push("draft.txt");goals["draft.txt"]=digest("fixed\n");
|
|
39
|
-
scenario+="Change the actual draft.txt anchor to fixed. A proposed old_string was absent; changing only new_string would not repair a missing anchor. ";
|
|
40
|
-
}else if(task==="recovery-runtime-acceptance"){
|
|
41
|
-
files["app-source.js"]='console.log("fixture preview ready");setInterval(()=>{},1000);\n';
|
|
42
|
-
files["build.ts"]='import {mkdir,copyFile} from "node:fs/promises";await mkdir("dist",{recursive:true});await copyFile("app-source.js","dist/app.js");\n';
|
|
43
|
-
goals["dist/app.js"]=digest(files["app-source.js"]);
|
|
44
|
-
scenario+="Build with bun run build, recording artifacts {paths:[\"dist/app.js\"],output_roots:[\"dist\"]}, and start the declared preview with target. A previous build or a missing target does not prove this instance is ready. Leave its human review pending. The harness closes the temporary preview after this trial. ";
|
|
45
|
-
}else if(task==="recovery-eof")scenario+="A suggested read starts at line 999; recover from the real file boundary rather than assuming that line exists. ";
|
|
46
|
-
files["verify.ts"]=`import {createHash} from "node:crypto";const goals=${JSON.stringify(goals)};for(const [path,sha] of Object.entries(goals)){const text=await Bun.file(path).text().catch(()=>"");if(createHash("sha256").update(text).digest("hex")!==sha)process.exit(1);}\n`;
|
|
47
|
-
return {task,repeat,files,digests:Object.fromEntries(Object.entries(files).filter(([path])=>!mutable.includes(path)).map(([path,text])=>[path,digest(text)])),
|
|
48
|
-
answerSha256:digest(answer),prompts:[scenario+"Preserve verifier and fixture inputs, run bun run gate, and report the actual acceptance state."],
|
|
49
|
-
recovery:{sourceReport:RECOVERY_SOURCE_REPORT,specification,answer,goalDigests:goals,mutableFiles:mutable}};
|
|
50
|
-
}
|
|
51
|
-
export function recoveryContract(plan:EvalPlan):AcceptanceContract{
|
|
52
|
-
if(!plan.runtime.bunExecutable)throw Error("recovery runtime identity missing");
|
|
53
|
-
return {version:1,id:"recovery-preview",criteria:[
|
|
54
|
-
{id:"gate",kind:"command",command:"bun run gate",description:"The requested outputs pass the frozen verifier"},
|
|
55
|
-
{id:"build",kind:"command",command:"bun run build",description:"The current preview is built"},
|
|
56
|
-
{id:"review",kind:"human",target:"preview",description:"The author reviews this exact running preview"}],
|
|
57
|
-
targets:[{id:"preview",build:"build",artifacts:{paths:["dist/app.js"],output_roots:["dist"]},entry:"dist/app.js",interpreter:plan.runtime.bunExecutable}]};
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
/** A deterministic driver tests the protocol and grader, not model competence. */
|
|
61
|
-
export function recoveryFixtureBackend(input:EvalFixture):ComparisonBackend{
|
|
62
|
-
if(!input.recovery)throw Error("recovery fixture metadata missing");
|
|
63
|
-
const answer=input.recovery.answer;let step=0;
|
|
64
|
-
return async payload=>{
|
|
65
|
-
const messages=payload["messages"] as {role:string;content?:unknown}[];
|
|
66
|
-
const text=messages.filter(message=>message.role==="tool").map(message=>typeof message.content==="string"?message.content:JSON.stringify(message.content)).join("\n");
|
|
67
|
-
const required=(pattern:RegExp)=>{const match=pattern.exec(text);if(!match)throw Error("fixture driver could not find the original tool receipt");return match[1]!;};
|
|
68
|
-
const calls:{name:string;input:unknown}[]=[],index=step++;
|
|
69
|
-
const call=(name:string,args:unknown)=>calls.push({name,input:args});
|
|
70
|
-
const read=()=>call("read",{path:"source.txt"}),write=()=>call("write",{path:"answer.txt",content:answer}),gate=()=>call("exec_command",{cmd:"bun run gate",yield_time_ms:1000});
|
|
71
|
-
switch(input.task){
|
|
72
|
-
case "recovery-missing-path":
|
|
73
|
-
if(index===0)call("edit",{old_string:"pending",new_string:answer.trim()});
|
|
74
|
-
if(index===1){read();call("read",{path:"answer.txt"});}
|
|
75
|
-
if(index===2)call("edit",{path:"answer.txt",old_string:"pending",new_string:answer.trim()});
|
|
76
|
-
if(index===3)gate();break;
|
|
77
|
-
case "recovery-stale-version":
|
|
78
|
-
if(index===0)call("edit",{path:"draft.txt",expected_sha256:digest("before\n"),old_string:"before",new_string:"after"});
|
|
79
|
-
if(index===1)call("read",{path:"draft.txt"});
|
|
80
|
-
if(index===2)call("edit",{path:"draft.txt",old_string:"before",new_string:"after"});
|
|
81
|
-
if(index===3)read();if(index===4)write();if(index===5)gate();break;
|
|
82
|
-
case "recovery-long-output":
|
|
83
|
-
if(index===0)read();
|
|
84
|
-
if(index===1)call("history",{artifact:required(/sha256: ([a-f0-9]{64})/),offset:4500});
|
|
85
|
-
if(index===2)write();if(index===3)gate();break;
|
|
86
|
-
case "recovery-api-mismatch":
|
|
87
|
-
if(index===0)call("exec_command",{cmd:"bun compat.ts --filter=safe",yield_time_ms:1000});
|
|
88
|
-
if(index===1)call("read",{path:"compat.ts"});
|
|
89
|
-
if(index===2)call("exec_command",{cmd:"bun compat.ts",yield_time_ms:1000});
|
|
90
|
-
if(index===3)gate();break;
|
|
91
|
-
case "recovery-closed-process":
|
|
92
|
-
if(index===0)call("exec_command",{cmd:"bun producer.ts",yield_time_ms:5000});
|
|
93
|
-
if(index===1)call("history",{recent:true,tool:"exec_command"});
|
|
94
|
-
if(index===2||index===3)call("write_stdin",{session_id:Number(required(/"session_id"\s*:\s*(\d+)/)),chars:index===2?"repeat\n":"",yield_time_ms:100});
|
|
95
|
-
if(index===4)write();if(index===5)gate();break;
|
|
96
|
-
case "recovery-unchanged-retry":
|
|
97
|
-
if(index===0||index===3)call("read",{path:"draft.txt"});
|
|
98
|
-
if(index===1||index===2)call("edit",{path:"draft.txt",old_string:"absent",new_string:index===1?"first":"second"});
|
|
99
|
-
if(index===4)call("edit",{path:"draft.txt",old_string:"present",new_string:"fixed"});
|
|
100
|
-
if(index===5)read();if(index===6)write();if(index===7)gate();break;
|
|
101
|
-
case "recovery-runtime-acceptance":
|
|
102
|
-
if(index===0||index===4)call("target",{action:"start",target:"preview",yield_time_ms:100});
|
|
103
|
-
if(index===1)read();if(index===2)write();
|
|
104
|
-
if(index===3)call("exec_command",{cmd:"bun run build",artifacts:{paths:["dist/app.js"],output_roots:["dist"]},yield_time_ms:1000});
|
|
105
|
-
if(index===5)gate();break;
|
|
106
|
-
case "recovery-eof":
|
|
107
|
-
if(index===0)call("read",{path:"source.txt",start:999});
|
|
108
|
-
if(index===1)read();if(index===2)write();if(index===3)gate();break;
|
|
109
|
-
default:throw Error("not a recovery fixture");
|
|
110
|
-
}
|
|
111
|
-
return {id:`recovery-${index}`,model:"eval-mechanical",choices:[{index:0,message:{role:"assistant",
|
|
112
|
-
content:calls.length?null:input.task==="recovery-runtime-acceptance"?"Preview preparation ended; the author's acceptance is pending.":"Recovery fixture finished.",
|
|
113
|
-
...(calls.length?{tool_calls:calls.map((item,i)=>({id:`recovery-${index}-${i}`,type:"function",function:{name:item.name,arguments:JSON.stringify(item.input)}}))}:{})},
|
|
114
|
-
finish_reason:calls.length?"tool_calls":"stop"}],usage:{prompt_tokens:200,completion_tokens:32,prompt_cache_hit_tokens:0}};
|
|
115
|
-
};
|
|
116
|
-
}
|
|
@@ -1,146 +0,0 @@
|
|
|
1
|
-
/** Q.19: fixed workloads and admission overlays share production tools and the v2 auditor. */
|
|
2
|
-
import { queryEvalVerifier } from "./query-eval-verifier.ts";
|
|
3
|
-
import { createHash } from "node:crypto";
|
|
4
|
-
import { join } from "node:path";
|
|
5
|
-
import { seededRandom, type EvalJob } from "../core/experiment.ts";
|
|
6
|
-
import { TOOL_MODE_TASKS, type ToolModeTask, type ToolModeMetrics } from "../core/tool-mode-protocol.ts";
|
|
7
|
-
import { queryEvalContract } from "../core/query-eval-contract.ts";
|
|
8
|
-
import { inputError } from "../core/tool-contract.ts";
|
|
9
|
-
import type { LogEvent, ArtifactRef } from "../core/events.ts";
|
|
10
|
-
import { readObservation } from "../store/observations.ts";
|
|
11
|
-
import { TOOL_DEFINITIONS, modelToolSchema, type ToolContext } from "./tools.ts";
|
|
12
|
-
import type { ToolSchema } from "./provider.ts";
|
|
13
|
-
import type { ComparisonBackend } from "./harness-comparison.ts";
|
|
14
|
-
import type { EvalFixture } from "./harness-eval.ts";
|
|
15
|
-
|
|
16
|
-
const digest = (text: string) => createHash("sha256").update(text).digest("hex");
|
|
17
|
-
export const TOOL_MODE_SYSTEM = "Solve the supplied data task using query over the complete source files. Preserve inputs and verifier. " +
|
|
18
|
-
"Use the declared query syntax. Inspect all failures and original evidence. " +
|
|
19
|
-
"Write only answer.txt, run bun run gate, and report the observed outcome. Do not delegate.";
|
|
20
|
-
export interface ToolModeFixture {
|
|
21
|
-
answer: string;
|
|
22
|
-
queries: { path: string; json: Record<string, unknown>; sql: Record<string, unknown> }[];
|
|
23
|
-
}
|
|
24
|
-
export function toolModeFixture(seed: number, task: ToolModeTask, repeat: number): EvalFixture {
|
|
25
|
-
const random = seededRandom(seed + TOOL_MODE_TASKS.indexOf(task) * 1000 + repeat);
|
|
26
|
-
const count = task === "tool-batch" ? 8 : 1, rows = task === "tool-small" ? 1 : task === "tool-batch" ? 25 : 9000;
|
|
27
|
-
const files: Record<string, string> = {}, queries: ToolModeFixture["queries"] = [], expected: { path: string; rows: unknown[] }[] = [];
|
|
28
|
-
for (let i = 0; i < count; i++) {
|
|
29
|
-
const path = "data-" + i + ".jsonl", data = Array.from({ length: rows }, (_, row) => ({
|
|
30
|
-
team: "team" + row % 4, enabled: row % 3 !== 0, amount: 1 + Math.floor(random() * 97),
|
|
31
|
-
}));
|
|
32
|
-
files[path] = data.map(row => JSON.stringify(row)).join("\n") + "\n";
|
|
33
|
-
const base = { paths: [path], format: "jsonl" };
|
|
34
|
-
if (task === "tool-small") {
|
|
35
|
-
queries.push({ path, json: { ...base, select: ["amount"] }, sql: { ...base, sql: "SELECT amount FROM source" } });
|
|
36
|
-
expected.push({ path, rows: [{ amount: data[0]!.amount }] });
|
|
37
|
-
} else if (task === "tool-batch") {
|
|
38
|
-
queries.push({ path, json: { ...base, aggregate: [{ op: "sum", field: "amount", as: "total" }] }, sql: { ...base, sql: "SELECT SUM(amount) AS total FROM source" } });
|
|
39
|
-
expected.push({ path, rows: [{ total: data.reduce((n, row) => n + row.amount, 0) }] });
|
|
40
|
-
} else {
|
|
41
|
-
queries.push({ path, json: { ...base, where: [{ field: "enabled", op: "eq", value: true }], group_by: ["team"],
|
|
42
|
-
aggregate: [{ op: "sum", field: "amount", as: "total" }], order_by: [{ field: "team" }] },
|
|
43
|
-
sql: { ...base, sql: "SELECT team, SUM(amount) AS total FROM source WHERE enabled = TRUE GROUP BY team ORDER BY team" } });
|
|
44
|
-
// Independent fixture arithmetic; do not ask the query kernel to grade itself.
|
|
45
|
-
expected.push({ path, rows: Array.from({ length: 4 }, (_, n) => {
|
|
46
|
-
const team = "team" + n;
|
|
47
|
-
return { team, total: data.filter(row => row.enabled && row.team === team).reduce((sum, row) => sum + row.amount, 0) };
|
|
48
|
-
}) });
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
const answer = JSON.stringify(expected) + "\n", answerSha256 = digest(answer);
|
|
52
|
-
files["package.json"] = JSON.stringify({ scripts: { gate: "bun verify.ts" } }) + "\n";
|
|
53
|
-
const contract = queryEvalContract(task, queries.map(query => query.path), answerSha256);
|
|
54
|
-
files["verify.ts"] = queryEvalVerifier(contract);
|
|
55
|
-
return { task, repeat, files, digests: Object.fromEntries(Object.entries(files).map(([path, body]) => [path, digest(body)])),
|
|
56
|
-
answerSha256, prompts: [contract.instruction], toolMode: { answer, queries } };
|
|
57
|
-
}
|
|
58
|
-
export type ToolModeSchemas = Record<"json" | "sql", ToolSchema[]>;
|
|
59
|
-
export function toolModeSchemas(): ToolModeSchemas {
|
|
60
|
-
const build = (syntax: "json" | "sql"): ToolSchema[] => {
|
|
61
|
-
const names = ["read", "query", "history", "write", "exec_command", "write_stdin", "note"];
|
|
62
|
-
return names.map(name => {
|
|
63
|
-
const definition = TOOL_DEFINITIONS.find(tool => tool.name === name)!;
|
|
64
|
-
if (name !== "query") return modelToolSchema(definition, names);
|
|
65
|
-
const schema = structuredClone(definition.input_schema) as ToolSchema["input_schema"] & { additionalProperties?: boolean };
|
|
66
|
-
const jsonKeys = ["where", "select", "group_by", "aggregate", "order_by"];
|
|
67
|
-
for (const key of syntax === "json" ? ["sql"] : jsonKeys) delete schema.properties[key];
|
|
68
|
-
if (syntax === "sql") schema.required.push("sql");
|
|
69
|
-
schema.additionalProperties = false;
|
|
70
|
-
return { name, description: definition.description + " This trial admits only " + syntax.toUpperCase() + " query syntax.", input_schema: schema };
|
|
71
|
-
});
|
|
72
|
-
};
|
|
73
|
-
return { json: build("json"), sql: build("sql") };
|
|
74
|
-
}
|
|
75
|
-
export const toolModeSchemaKey = (job: EvalJob): keyof ToolModeSchemas => job.policy.querySyntax === "sql" ? "sql" : "json";
|
|
76
|
-
/** Schema visibility is not authority: the same frozen restriction runs before every admitted query. */
|
|
77
|
-
export function toolModeHooks(schemas: readonly ToolSchema[]): NonNullable<ToolContext["extensionHooks"]> {
|
|
78
|
-
const query = schemas.find(schema => schema.name === "query")!;
|
|
79
|
-
return { async before(name, input) {
|
|
80
|
-
if (name !== "query") return undefined;
|
|
81
|
-
const error = inputError(query.input_schema, input);
|
|
82
|
-
const properties = (query.input_schema as { properties: Record<string, unknown> }).properties;
|
|
83
|
-
const extra = input && typeof input === "object" ? Object.keys(input).find(key => !Object.hasOwn(properties, key)) : undefined;
|
|
84
|
-
return error || extra ? "Frozen query syntax restriction: " + (error ?? "unsupported field " + extra) : undefined;
|
|
85
|
-
}, async event() {} };
|
|
86
|
-
}
|
|
87
|
-
/** Oracle driver tests the machinery; neither its answers nor call choices are model evidence. */
|
|
88
|
-
export function toolModeBackend(input: EvalFixture): ComparisonBackend {
|
|
89
|
-
const fixture = input.toolMode!;
|
|
90
|
-
let step = 0;
|
|
91
|
-
return async payload => {
|
|
92
|
-
const declarations = payload["tools"] as { function: { name: string; parameters: { properties: Record<string, unknown> } } }[];
|
|
93
|
-
const syntax = declarations.find(tool => tool.function.name === "query")!.function.parameters.properties["sql"] ? "sql" : "json";
|
|
94
|
-
const calls: { name: string; input: unknown }[] = [], index = step++;
|
|
95
|
-
if (index === 0) {
|
|
96
|
-
for (const query of fixture.queries) calls.push({ name: "query", input: query[syntax] });
|
|
97
|
-
}
|
|
98
|
-
if (index === 1) {
|
|
99
|
-
const messages = payload["messages"] as { role: string; content?: unknown }[];
|
|
100
|
-
const observed = messages.filter(message => message.role === "tool").flatMap(message => {
|
|
101
|
-
const value = JSON.parse(String(message.content));
|
|
102
|
-
return [value];
|
|
103
|
-
}) as { sources: { path: string }[]; rows: unknown[] }[];
|
|
104
|
-
const answer = fixture.queries.map(query => {
|
|
105
|
-
const value = observed.find(value => value.sources?.length === 1 && value.sources[0]!.path === query.path);
|
|
106
|
-
if (!value) throw Error("mechanical driver lacks a successful source query receipt");
|
|
107
|
-
return { path: query.path, rows: value.rows };
|
|
108
|
-
});
|
|
109
|
-
calls.push({ name: "write", input: { path: "answer.txt", content: JSON.stringify(answer) + "\n" } });
|
|
110
|
-
}
|
|
111
|
-
if (index === 2) calls.push({ name: "exec_command", input: { cmd: "bun run gate", yield_time_ms: 1000 } });
|
|
112
|
-
return { id: "tool-mode-" + index, model: "eval-mechanical", choices: [{ index: 0, message: {
|
|
113
|
-
role: "assistant", content: calls.length ? null : "Mechanical protocol fixture finished.",
|
|
114
|
-
...(calls.length ? { tool_calls: calls.map((call, i) => ({ id: "mode-" + index + "-" + i, type: "function",
|
|
115
|
-
function: { name: call.name, arguments: JSON.stringify(call.input) } })) } : {}),
|
|
116
|
-
}, finish_reason: calls.length ? "tool_calls" : "stop" }], usage: { prompt_tokens: 200, completion_tokens: 32, prompt_cache_hit_tokens: 0 } };
|
|
117
|
-
};
|
|
118
|
-
}
|
|
119
|
-
export async function toolModeMetrics(directory: string, input: EvalFixture, events: readonly LogEvent[], elapsedMs: number): Promise<ToolModeMetrics> {
|
|
120
|
-
if (!Number.isFinite(elapsedMs) || elapsedMs < 0) throw Error("missing tool mode elapsed observation");
|
|
121
|
-
const fixture = input.toolMode!, invocations = new Map(events.filter(event => event.t === "tool_invocation").map(event => [event.id, event]));
|
|
122
|
-
const sources = new Set<string>();
|
|
123
|
-
const original = async (ref: ArtifactRef): Promise<unknown> => {
|
|
124
|
-
const parts: Uint8Array[] = [];
|
|
125
|
-
for (let offset = 0;;) {
|
|
126
|
-
const page = await readObservation(join(directory, "observations"), ref.sha256, offset, 1024 * 1024);
|
|
127
|
-
parts.push(Buffer.from(page.base64, "base64"));
|
|
128
|
-
if (page.next === null) break;
|
|
129
|
-
offset = page.next;
|
|
130
|
-
}
|
|
131
|
-
return JSON.parse(Buffer.concat(parts).toString("utf8"));
|
|
132
|
-
};
|
|
133
|
-
for (const event of events) if (event.t === "tool_result" && event.ok && event.invocation_ref) {
|
|
134
|
-
const invocation = invocations.get(event.invocation_ref.event);
|
|
135
|
-
if (!invocation || invocation.call_id !== event.call_id || invocation.name !== "query") continue;
|
|
136
|
-
const value = event.data_ref ? await original(event.data_ref) : event.content_ref ? await original(event.content_ref) : event.data;
|
|
137
|
-
if (!value || typeof value !== "object" || !("sources" in value) || !Array.isArray(value.sources)) continue;
|
|
138
|
-
for (const source of value.sources as { path?: string; sha256?: string }[]) {
|
|
139
|
-
if (typeof source.path === "string" && fixture.queries.some(query => query.path === source.path) && input.digests[source.path] === source.sha256) sources.add(source.path);
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
const calls = [...invocations.values()], sourcePaths = [...sources].sort(), requiredSourcePaths = fixture.queries.map(query => query.path).sort();
|
|
143
|
-
return { elapsedMs, queryCalls: calls.filter(call => call.name === "query").length,
|
|
144
|
-
primitiveCalls: calls.length, sourcePaths, requiredSourcePaths,
|
|
145
|
-
sourceComplete: requiredSourcePaths.every(path => sources.has(path)) };
|
|
146
|
-
}
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
/** Q.14: an explicit signal owner records the first interruption independent of exception paths. */
|
|
2
|
-
import type { TrialInterruption, TrialLifecycle } from "../core/trial-lifecycle.ts";
|
|
3
|
-
|
|
4
|
-
/** A bad deadline must not strand the gateway acquired immediately before its clock starts. */
|
|
5
|
-
export async function openTrialControl(timeoutMs:number,caller:AbortSignal|undefined,disposeOnFailure:()=>Promise<void>):Promise<TrialControl> {
|
|
6
|
-
try {return new TrialControl(timeoutMs,caller);}
|
|
7
|
-
catch(error){await disposeOnFailure();throw error;}
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
export class TrialControl {
|
|
11
|
-
private readonly controller=new AbortController();
|
|
12
|
-
readonly signal=this.controller.signal;
|
|
13
|
-
private readonly deadline:AbortSignal;
|
|
14
|
-
private readonly caller:AbortSignal|undefined;
|
|
15
|
-
private phase:TrialInterruption["phase"]="initialization";
|
|
16
|
-
private requests=0;
|
|
17
|
-
private interrupted:TrialInterruption|null=null;
|
|
18
|
-
private active=true;
|
|
19
|
-
private readonly onDeadline=()=>this.stop("deadline");
|
|
20
|
-
private readonly onCaller=()=>this.stop("caller");
|
|
21
|
-
constructor(timeoutMs:number,caller?:AbortSignal) {
|
|
22
|
-
this.deadline=AbortSignal.timeout(timeoutMs);this.caller=caller;
|
|
23
|
-
this.deadline.addEventListener("abort",this.onDeadline,{once:true});
|
|
24
|
-
caller?.addEventListener("abort",this.onCaller,{once:true});
|
|
25
|
-
// An already-cancelled caller precedes the newly-created local deadline.
|
|
26
|
-
if(caller?.aborted)this.onCaller();else if(this.deadline.aborted)this.onDeadline();
|
|
27
|
-
}
|
|
28
|
-
private stop(source:TrialInterruption["source"]):void {
|
|
29
|
-
if(!this.active||this.interrupted)return;
|
|
30
|
-
this.interrupted={source,phase:this.phase,backendRequests:this.requests};
|
|
31
|
-
this.controller.abort();
|
|
32
|
-
}
|
|
33
|
-
enterExecution():void {this.phase="execution";}
|
|
34
|
-
beginBackend():void {
|
|
35
|
-
if(!this.active||this.signal.aborted)throw Error("trial no longer accepts backend requests");
|
|
36
|
-
this.requests++;
|
|
37
|
-
}
|
|
38
|
-
finishWork():void {
|
|
39
|
-
this.active=false;
|
|
40
|
-
this.deadline.removeEventListener("abort",this.onDeadline);this.caller?.removeEventListener("abort",this.onCaller);
|
|
41
|
-
}
|
|
42
|
-
observe(cleanup:TrialLifecycle["cleanup"]):TrialLifecycle {
|
|
43
|
-
return {version:1,backendRequests:this.requests,interruption:this.interrupted?{...this.interrupted}:null,cleanup};
|
|
44
|
-
}
|
|
45
|
-
}
|
|
@@ -1,141 +0,0 @@
|
|
|
1
|
-
/** N.13: real sessions with independent file verdicts at every stage. */
|
|
2
|
-
import { appendFile, mkdir, readFile, writeFile } from "node:fs/promises";
|
|
3
|
-
import { join, dirname } from "node:path";
|
|
4
|
-
import { createHash } from "node:crypto";
|
|
5
|
-
import { verifyWindowArtifact, windowPrompt, type WindowFixture } from "../core/window-trial.ts";
|
|
6
|
-
import type { BenchmarkPrice } from "../core/benchmark.ts";
|
|
7
|
-
import { projectContext } from "../core/project.ts";
|
|
8
|
-
import { readNodeHistory } from "../store/restore.ts";
|
|
9
|
-
import { manifestPath, nodePath } from "../store/log.ts";
|
|
10
|
-
import { Session, type SessionConfig } from "./loop.ts";
|
|
11
|
-
import { buildRequest, OpenAICompletionsProvider } from "./openai.ts";
|
|
12
|
-
import type { Provider, ProviderConfig } from "./provider.ts";
|
|
13
|
-
import { COMPARISON_SYSTEM, COMPARISON_TOOLS, comparisonGateway, comparisonPayload, comparisonTools, type ComparisonBackend, type ComparisonLedger, type GatewayRecord } from "./harness-comparison.ts";
|
|
14
|
-
import { DEFAULT_TOOL_CONTEXT } from "./tools.ts";
|
|
15
|
-
|
|
16
|
-
const sha = (body: string) => createHash("sha256").update(body).digest("hex");
|
|
17
|
-
export interface WindowStageResult {
|
|
18
|
-
stage: number; status: "answered" | "stopped" | "balance" | "error"; reason: string;
|
|
19
|
-
artifact: { ok: boolean; reason: string }; artifactSha256: string | null;
|
|
20
|
-
fullPageRead: boolean; inputsUnchanged: boolean; passed: boolean;
|
|
21
|
-
lastEvent: string | null; inputs: Record<string, string | null>;
|
|
22
|
-
requestBegin: number; requestEnd: number; nodeBefore: number; nodeAfter: number;
|
|
23
|
-
restart: { beforeSha256: string; afterSha256: string } | null;
|
|
24
|
-
}
|
|
25
|
-
export interface WindowTrialResult {
|
|
26
|
-
policy: "windowed" | "continuous"; planned: number; stages: WindowStageResult[]; unrun: number;
|
|
27
|
-
cuts: number; firstCutStage: number | null; restartedAt: number | null;
|
|
28
|
-
requests: number; knownCost: number; reservedUnknown: number; peakPrompt: number;
|
|
29
|
-
promptTokens: number; cacheTokens: number; outputTokens: number; prefixMutationsWithinNode: number;
|
|
30
|
-
toolExecutions: number; toolFailures: number; wallMs: number;
|
|
31
|
-
stopped: "budget" | "balance" | "error" | "limit" | null;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
export async function runWindowTrial(options: {
|
|
35
|
-
directory: string; fixture: WindowFixture; policy: WindowTrialResult["policy"];
|
|
36
|
-
config: ProviderConfig; price: BenchmarkPrice; ledger: ComparisonLedger; backend: ComparisonBackend;
|
|
37
|
-
maxTurns: number; stageTimeoutMs: number; maxOutputBytes: number;
|
|
38
|
-
/** windowed restarts after its first cut; continuous uses that same stage. */
|
|
39
|
-
restartAfterStage?: number | null;
|
|
40
|
-
onProgress?: (event: Record<string, unknown>) => Promise<void>;
|
|
41
|
-
}): Promise<WindowTrialResult> {
|
|
42
|
-
const started = performance.now(), workspace = join(options.directory, "workspace"), logDir = join(options.directory, "log");
|
|
43
|
-
await mkdir(workspace, { recursive: true }); await mkdir(logDir);
|
|
44
|
-
const result: WindowTrialResult = { policy: options.policy, planned: options.fixture.pages.length, stages: [], unrun: options.fixture.pages.length,
|
|
45
|
-
cuts: 0, firstCutStage: null, restartedAt: null, requests: 0, knownCost: 0, reservedUnknown: 0, peakPrompt: 0,
|
|
46
|
-
promptTokens: 0, cacheTokens: 0, outputTokens: 0, prefixMutationsWithinNode: 0, toolExecutions: 0, toolFailures: 0, wallMs: 0, stopped: null };
|
|
47
|
-
let stage = 0, lastNode: number | null = null, sentNode = 0;
|
|
48
|
-
const gateway = comparisonGateway({ config: options.config, price: options.price, ledger: options.ledger,
|
|
49
|
-
maxRequests: options.maxTurns * options.fixture.pages.length, backend: options.backend, record: async record => {
|
|
50
|
-
await appendFile(join(options.directory, "requests.jsonl"), JSON.stringify({ stage, node: sentNode, ...record }) + "\n");
|
|
51
|
-
if (record.kind === "settled") {
|
|
52
|
-
result.requests++;
|
|
53
|
-
if (record.measured === null) result.reservedUnknown += record.reservation; else result.knownCost += record.measured;
|
|
54
|
-
const usage = record.usage;
|
|
55
|
-
if (usage) {
|
|
56
|
-
const prompt = usage.input_tokens + usage.cache_read_input_tokens + usage.cache_creation_input_tokens;
|
|
57
|
-
result.peakPrompt = Math.max(result.peakPrompt, prompt); result.promptTokens += prompt;
|
|
58
|
-
result.cacheTokens += usage.cache_read_input_tokens; result.outputTokens += usage.output_tokens;
|
|
59
|
-
}
|
|
60
|
-
if (lastNode === sentNode && record.prefixUnchanged === false) result.prefixMutationsWithinNode++;
|
|
61
|
-
lastNode = sentNode;
|
|
62
|
-
}
|
|
63
|
-
await options.onProgress?.({ phase: record.kind, policy: options.policy, stage, request: record.request,
|
|
64
|
-
measured: record.measured, failure: record.failure, result });
|
|
65
|
-
} });
|
|
66
|
-
const config = { ...options.config, spec: { ...options.config.spec, baseUrl: gateway.url } };
|
|
67
|
-
const actual = new OpenAICompletionsProvider(config, gateway.token, { retry: { maxRetries: 0, maxRetryDelayMs: 0 } });
|
|
68
|
-
const sessionConfig: SessionConfig = { logDir, blobDir: join(options.directory, "blobs"),
|
|
69
|
-
frozen: { system: COMPARISON_SYSTEM, tools: comparisonTools() },
|
|
70
|
-
tools: { ...DEFAULT_TOOL_CONTEXT, root: workspace, allow: COMPARISON_TOOLS, maxOutputBytes: options.maxOutputBytes,
|
|
71
|
-
outputStore: join(options.directory, "observations") }, contextPolicy: options.policy, maxTurns: options.maxTurns, harnessReviews: false };
|
|
72
|
-
const provider: Provider = { config, async send(frozen, messages, node, signal, onDelta) {
|
|
73
|
-
sentNode = node;
|
|
74
|
-
await appendFile(join(options.directory, "dispatch.jsonl"), JSON.stringify({ stage, request: gateway.requests + 1, node,
|
|
75
|
-
lastEvent: session.state.events.at(-1)?.id,
|
|
76
|
-
inputSha256: sha(JSON.stringify(comparisonPayload(config, buildRequest(config, frozen, messages).messages, true))) }) + "\n");
|
|
77
|
-
return actual.send(frozen, messages, node, signal, onDelta);
|
|
78
|
-
} };
|
|
79
|
-
let session = new Session(sessionConfig, provider);
|
|
80
|
-
try {
|
|
81
|
-
await session.start();
|
|
82
|
-
for (const page of options.fixture.pages) {
|
|
83
|
-
if (options.ledger.halted) break;
|
|
84
|
-
stage = page.stage;
|
|
85
|
-
const file = join(workspace, page.path); await mkdir(dirname(file), { recursive: true });
|
|
86
|
-
await writeFile(file, page.body, { flag: "wx" });
|
|
87
|
-
const row: WindowStageResult = { stage, status: "stopped", reason: "no completion", artifact: { ok: false, reason: "not checked" },
|
|
88
|
-
artifactSha256: null, fullPageRead: false, inputsUnchanged: false, passed: false, lastEvent: null, inputs: {}, requestBegin: gateway.requests,
|
|
89
|
-
requestEnd: gateway.requests, nodeBefore: session.state.node, nodeAfter: session.state.node, restart: null };
|
|
90
|
-
const signal = AbortSignal.timeout(options.stageTimeoutMs);
|
|
91
|
-
try {
|
|
92
|
-
for await (const event of session.send(windowPrompt(options.fixture, stage), signal)) {
|
|
93
|
-
if (event.kind === "cut") { result.cuts++; result.firstCutStage ??= stage; }
|
|
94
|
-
if (event.kind === "tool_call") result.toolExecutions++;
|
|
95
|
-
if (event.kind === "tool_result" && event.ok === false) result.toolFailures++;
|
|
96
|
-
if (event.kind === "done") row.reason = event.reason ?? "unknown completion";
|
|
97
|
-
}
|
|
98
|
-
if (["stop", "end_turn"].includes(row.reason)) row.status = "answered";
|
|
99
|
-
} catch {
|
|
100
|
-
row.status = gateway.stopped === "balance" ? "balance" : "error";
|
|
101
|
-
row.reason = signal.aborted ? "stage deadline" : gateway.stopped === "balance" ? "upstream balance; awaiting topup" : "trial stopped on error";
|
|
102
|
-
}
|
|
103
|
-
row.nodeAfter = session.state.node; row.requestEnd = gateway.requests;
|
|
104
|
-
const body = await readFile(join(workspace, "ledger.json"), "utf8").catch(() => null);
|
|
105
|
-
row.artifact = verifyWindowArtifact(body, page.expected); row.artifactSha256 = body === null ? null : sha(body);
|
|
106
|
-
// Preserve failed outputs as well, before the next stage can repair them.
|
|
107
|
-
await writeFile(join(options.directory, `artifact-${stage}.json`), JSON.stringify({ body }), { flag: "wx" });
|
|
108
|
-
const history = await readNodeHistory(logDir, session.state.node, undefined, "context");
|
|
109
|
-
if (!history.ok) throw new Error(history.error);
|
|
110
|
-
const events = history.nodes.flatMap(node => node.events);
|
|
111
|
-
row.lastEvent = events.at(-1)?.id ?? null;
|
|
112
|
-
row.fullPageRead = events.some(event => event.t === "tool_result" && event.ok && event.effect?.kind === "read" &&
|
|
113
|
-
event.effect.path === page.path && event.effect.sha256 === sha(page.body) && event.effect.ranges?.some(range => range[0] === 1 && range[1] === page.body.split("\n").length) &&
|
|
114
|
-
// Archival is independent of truncation. Verify what the model actually received, including the middle.
|
|
115
|
-
event.content.includes(page.body.split("\n").map((line, index) => `${index + 1}\t${line}`).join("\n")));
|
|
116
|
-
for (const prior of options.fixture.pages.slice(0, stage + 1)) {
|
|
117
|
-
const content = await readFile(join(workspace, prior.path), "utf8").catch(() => null);
|
|
118
|
-
row.inputs[prior.path] = content === null ? null : sha(content);
|
|
119
|
-
}
|
|
120
|
-
row.inputsUnchanged = options.fixture.pages.slice(0, stage + 1).every(prior => row.inputs[prior.path] === sha(prior.body));
|
|
121
|
-
row.passed = row.status === "answered" && row.artifact.ok && row.fullPageRead && row.inputsUnchanged;
|
|
122
|
-
const restart = options.policy === "windowed" ? result.firstCutStage : options.restartAfterStage;
|
|
123
|
-
if (row.status === "answered" && result.restartedAt === null && restart === stage) {
|
|
124
|
-
const view = () => sha(JSON.stringify(projectContext({ frozen: session.config.frozen, handoff: session.state.handoff, events: session.state.events,
|
|
125
|
-
logHint: { manifestPath: manifestPath(logDir), segmentPath: nodePath(logDir, Math.max(0, session.state.node - 1)) } })));
|
|
126
|
-
const beforeSha256 = view(); session = new Session(sessionConfig, provider); await session.start();
|
|
127
|
-
row.restart = { beforeSha256, afterSha256: view() }; result.restartedAt = stage;
|
|
128
|
-
if (row.restart.beforeSha256 !== row.restart.afterSha256) throw new Error("restart changed the context projection");
|
|
129
|
-
}
|
|
130
|
-
result.stages.push(row); result.unrun = result.planned - result.stages.length;
|
|
131
|
-
await writeFile(join(options.directory, `stage-${stage}.json`), JSON.stringify(row, null, 2) + "\n", { flag: "wx" });
|
|
132
|
-
await options.onProgress?.({ phase: "stage", policy: options.policy, stage, result });
|
|
133
|
-
if (row.status !== "answered") break;
|
|
134
|
-
}
|
|
135
|
-
} finally { await gateway.close(); }
|
|
136
|
-
result.stopped = gateway.stopped; result.wallMs = performance.now() - started;
|
|
137
|
-
await writeFile(join(options.directory, "result.json"), JSON.stringify(result, null, 2) + "\n", { flag: "wx" });
|
|
138
|
-
return result;
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
export type WindowRequest = GatewayRecord & { stage: number; node: number };
|
package/src/store/git-branch.ts
DELETED
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Current git branch for the status line.
|
|
3
|
-
*
|
|
4
|
-
* I/O lives here because tui cannot do I/O. Failure is silent: a missing
|
|
5
|
-
* binary, a directory that is not a repo, a detached HEAD, or a hang must
|
|
6
|
-
* not throw and must not stall TUI attach -- the status line just omits
|
|
7
|
-
* the branch.
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Assumed, not measured. `git symbolic-ref` is a local read; 200ms is long
|
|
12
|
-
* enough for a cold disk and short enough that a hung credential helper
|
|
13
|
-
* cannot freeze the first paint.
|
|
14
|
-
*/
|
|
15
|
-
export const GIT_BRANCH_TIMEOUT_MS = 200;
|
|
16
|
-
|
|
17
|
-
export function currentBranch(
|
|
18
|
-
cwd: string,
|
|
19
|
-
command = "git",
|
|
20
|
-
timeoutMs = GIT_BRANCH_TIMEOUT_MS,
|
|
21
|
-
): string | null {
|
|
22
|
-
try {
|
|
23
|
-
const p = Bun.spawnSync([command, "symbolic-ref", "--short", "HEAD"], {
|
|
24
|
-
cwd,
|
|
25
|
-
timeout: timeoutMs,
|
|
26
|
-
stdout: "pipe",
|
|
27
|
-
stderr: "pipe",
|
|
28
|
-
});
|
|
29
|
-
if (p.exitCode !== 0) return null;
|
|
30
|
-
const name = new TextDecoder().decode(p.stdout).trim();
|
|
31
|
-
return name.length > 0 ? name : null;
|
|
32
|
-
} catch {
|
|
33
|
-
return null;
|
|
34
|
-
}
|
|
35
|
-
}
|
package/src/tui/ui/testing.ts
DELETED