@osfactory/har 0.10.0 → 0.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +20 -9
- package/dist/index.js +1469 -460
- package/dist/templates/agent-skills/har-maintain.md +2 -0
- package/dist/templates/agent-skills/setup-har.md +13 -2
- package/dist/templates/har-boilerplate/CLAUDE.agent.md +4 -2
- package/dist/templates/har-boilerplate/STAGES.md +91 -0
- package/dist/templates/har-boilerplate/agent-slot.sh +46 -6
- package/dist/templates/har-boilerplate/launch.sh +2 -1
- package/dist/templates/har-boilerplate/stages/README.sh +7 -5
- package/dist/templates/har-boilerplate/verify.sh +9 -2
- package/dist/templates/har-boilerplate-cli/CLAUDE.agent.md +3 -1
- package/dist/templates/har-boilerplate-cli/STAGES.md +91 -0
- package/dist/templates/har-boilerplate-cli/agent-slot.sh +49 -9
- package/dist/templates/har-boilerplate-cli/stages/README.sh +7 -5
- package/dist/templates/har-boilerplate-cli/verify.sh +9 -2
- package/dist/templates/har-boilerplate-ios/CLAUDE.agent.md +2 -0
- package/dist/templates/har-boilerplate-ios/STAGES.md +91 -0
- package/dist/templates/har-boilerplate-ios/agent-slot.sh +35 -6
- package/dist/templates/har-boilerplate-ios/stages/README.sh +6 -7
- package/dist/templates/har-boilerplate-ios/verify.sh +9 -3
- package/dist/templates/stage-templates/custom-stage-skeleton.sh +65 -0
- package/dist/templates/stage-templates/playwright/template.manifest.json +9 -1
- package/dist/templates/stage-templates/rocketsim/template.manifest.json +2 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -765,14 +765,14 @@ var require_url_state_machine = __commonJS({
|
|
|
765
765
|
return url.replace(/\u0009|\u000A|\u000D/g, "");
|
|
766
766
|
}
|
|
767
767
|
function shortenPath(url) {
|
|
768
|
-
const
|
|
769
|
-
if (
|
|
768
|
+
const path47 = url.path;
|
|
769
|
+
if (path47.length === 0) {
|
|
770
770
|
return;
|
|
771
771
|
}
|
|
772
|
-
if (url.scheme === "file" &&
|
|
772
|
+
if (url.scheme === "file" && path47.length === 1 && isNormalizedWindowsDriveLetter(path47[0])) {
|
|
773
773
|
return;
|
|
774
774
|
}
|
|
775
|
-
|
|
775
|
+
path47.pop();
|
|
776
776
|
}
|
|
777
777
|
function includesCredentials(url) {
|
|
778
778
|
return url.username !== "" || url.password !== "";
|
|
@@ -2092,7 +2092,7 @@ var require_lib2 = __commonJS({
|
|
|
2092
2092
|
let accum = [];
|
|
2093
2093
|
let accumBytes = 0;
|
|
2094
2094
|
let abort = false;
|
|
2095
|
-
return new Body.Promise(function(
|
|
2095
|
+
return new Body.Promise(function(resolve33, reject) {
|
|
2096
2096
|
let resTimeout;
|
|
2097
2097
|
if (_this4.timeout) {
|
|
2098
2098
|
resTimeout = setTimeout(function() {
|
|
@@ -2126,7 +2126,7 @@ var require_lib2 = __commonJS({
|
|
|
2126
2126
|
}
|
|
2127
2127
|
clearTimeout(resTimeout);
|
|
2128
2128
|
try {
|
|
2129
|
-
|
|
2129
|
+
resolve33(Buffer.concat(accum, accumBytes));
|
|
2130
2130
|
} catch (err) {
|
|
2131
2131
|
reject(new FetchError(`Could not create Buffer from response body for ${_this4.url}: ${err.message}`, "system", err));
|
|
2132
2132
|
}
|
|
@@ -2801,7 +2801,7 @@ var require_lib2 = __commonJS({
|
|
|
2801
2801
|
throw new Error("native promise missing, set fetch.Promise to your favorite alternative");
|
|
2802
2802
|
}
|
|
2803
2803
|
Body.Promise = fetch3.Promise;
|
|
2804
|
-
return new fetch3.Promise(function(
|
|
2804
|
+
return new fetch3.Promise(function(resolve33, reject) {
|
|
2805
2805
|
const request = new Request3(url, opts);
|
|
2806
2806
|
const options = getNodeRequestOptions(request);
|
|
2807
2807
|
const send = (options.protocol === "https:" ? https : http).request;
|
|
@@ -2934,7 +2934,7 @@ var require_lib2 = __commonJS({
|
|
|
2934
2934
|
requestOpts.body = void 0;
|
|
2935
2935
|
requestOpts.headers.delete("content-length");
|
|
2936
2936
|
}
|
|
2937
|
-
|
|
2937
|
+
resolve33(fetch3(new Request3(locationURL, requestOpts)));
|
|
2938
2938
|
finalize();
|
|
2939
2939
|
return;
|
|
2940
2940
|
}
|
|
@@ -2955,7 +2955,7 @@ var require_lib2 = __commonJS({
|
|
|
2955
2955
|
const codings = headers.get("Content-Encoding");
|
|
2956
2956
|
if (!request.compress || request.method === "HEAD" || codings === null || res.statusCode === 204 || res.statusCode === 304) {
|
|
2957
2957
|
response = new Response3(body, response_options);
|
|
2958
|
-
|
|
2958
|
+
resolve33(response);
|
|
2959
2959
|
return;
|
|
2960
2960
|
}
|
|
2961
2961
|
const zlibOptions = {
|
|
@@ -2965,7 +2965,7 @@ var require_lib2 = __commonJS({
|
|
|
2965
2965
|
if (codings == "gzip" || codings == "x-gzip") {
|
|
2966
2966
|
body = body.pipe(zlib.createGunzip(zlibOptions));
|
|
2967
2967
|
response = new Response3(body, response_options);
|
|
2968
|
-
|
|
2968
|
+
resolve33(response);
|
|
2969
2969
|
return;
|
|
2970
2970
|
}
|
|
2971
2971
|
if (codings == "deflate" || codings == "x-deflate") {
|
|
@@ -2977,12 +2977,12 @@ var require_lib2 = __commonJS({
|
|
|
2977
2977
|
body = body.pipe(zlib.createInflateRaw());
|
|
2978
2978
|
}
|
|
2979
2979
|
response = new Response3(body, response_options);
|
|
2980
|
-
|
|
2980
|
+
resolve33(response);
|
|
2981
2981
|
});
|
|
2982
2982
|
raw.on("end", function() {
|
|
2983
2983
|
if (!response) {
|
|
2984
2984
|
response = new Response3(body, response_options);
|
|
2985
|
-
|
|
2985
|
+
resolve33(response);
|
|
2986
2986
|
}
|
|
2987
2987
|
});
|
|
2988
2988
|
return;
|
|
@@ -2990,11 +2990,11 @@ var require_lib2 = __commonJS({
|
|
|
2990
2990
|
if (codings == "br" && typeof zlib.createBrotliDecompress === "function") {
|
|
2991
2991
|
body = body.pipe(zlib.createBrotliDecompress());
|
|
2992
2992
|
response = new Response3(body, response_options);
|
|
2993
|
-
|
|
2993
|
+
resolve33(response);
|
|
2994
2994
|
return;
|
|
2995
2995
|
}
|
|
2996
2996
|
response = new Response3(body, response_options);
|
|
2997
|
-
|
|
2997
|
+
resolve33(response);
|
|
2998
2998
|
});
|
|
2999
2999
|
writeToStream(req, request);
|
|
3000
3000
|
});
|
|
@@ -6351,14 +6351,14 @@ __export(fileFromPath_exports, {
|
|
|
6351
6351
|
fileFromPathSync: () => fileFromPathSync,
|
|
6352
6352
|
isFile: () => isFile
|
|
6353
6353
|
});
|
|
6354
|
-
function createFileFromPath(
|
|
6354
|
+
function createFileFromPath(path47, { mtimeMs, size }, filenameOrOptions, options = {}) {
|
|
6355
6355
|
let filename;
|
|
6356
6356
|
if (isPlainObject_default2(filenameOrOptions)) {
|
|
6357
6357
|
[options, filename] = [filenameOrOptions, void 0];
|
|
6358
6358
|
} else {
|
|
6359
6359
|
filename = filenameOrOptions;
|
|
6360
6360
|
}
|
|
6361
|
-
const file = new FileFromPath({ path:
|
|
6361
|
+
const file = new FileFromPath({ path: path47, size, lastModified: mtimeMs });
|
|
6362
6362
|
if (!filename) {
|
|
6363
6363
|
filename = file.name;
|
|
6364
6364
|
}
|
|
@@ -6367,13 +6367,13 @@ function createFileFromPath(path40, { mtimeMs, size }, filenameOrOptions, option
|
|
|
6367
6367
|
lastModified: file.lastModified
|
|
6368
6368
|
});
|
|
6369
6369
|
}
|
|
6370
|
-
function fileFromPathSync(
|
|
6371
|
-
const stats = (0, import_fs5.statSync)(
|
|
6372
|
-
return createFileFromPath(
|
|
6370
|
+
function fileFromPathSync(path47, filenameOrOptions, options = {}) {
|
|
6371
|
+
const stats = (0, import_fs5.statSync)(path47);
|
|
6372
|
+
return createFileFromPath(path47, stats, filenameOrOptions, options);
|
|
6373
6373
|
}
|
|
6374
|
-
async function fileFromPath2(
|
|
6375
|
-
const stats = await import_fs5.promises.stat(
|
|
6376
|
-
return createFileFromPath(
|
|
6374
|
+
async function fileFromPath2(path47, filenameOrOptions, options) {
|
|
6375
|
+
const stats = await import_fs5.promises.stat(path47);
|
|
6376
|
+
return createFileFromPath(path47, stats, filenameOrOptions, options);
|
|
6377
6377
|
}
|
|
6378
6378
|
var import_fs5, import_path5, import_node_domexception, __classPrivateFieldSet5, __classPrivateFieldGet6, _FileFromPath_path, _FileFromPath_start, MESSAGE, FileFromPath;
|
|
6379
6379
|
var init_fileFromPath = __esm({
|
|
@@ -9384,7 +9384,7 @@ var require_compile = __commonJS({
|
|
|
9384
9384
|
const schOrFunc = root.refs[ref];
|
|
9385
9385
|
if (schOrFunc)
|
|
9386
9386
|
return schOrFunc;
|
|
9387
|
-
let _sch =
|
|
9387
|
+
let _sch = resolve33.call(this, root, ref);
|
|
9388
9388
|
if (_sch === void 0) {
|
|
9389
9389
|
const schema = (_a3 = root.localRefs) === null || _a3 === void 0 ? void 0 : _a3[ref];
|
|
9390
9390
|
const { schemaId } = this.opts;
|
|
@@ -9411,7 +9411,7 @@ var require_compile = __commonJS({
|
|
|
9411
9411
|
function sameSchemaEnv(s1, s2) {
|
|
9412
9412
|
return s1.schema === s2.schema && s1.root === s2.root && s1.baseId === s2.baseId;
|
|
9413
9413
|
}
|
|
9414
|
-
function
|
|
9414
|
+
function resolve33(root, ref) {
|
|
9415
9415
|
let sch;
|
|
9416
9416
|
while (typeof (sch = this.refs[ref]) == "string")
|
|
9417
9417
|
ref = sch;
|
|
@@ -9629,8 +9629,8 @@ var require_utils2 = __commonJS({
|
|
|
9629
9629
|
}
|
|
9630
9630
|
return ind;
|
|
9631
9631
|
}
|
|
9632
|
-
function removeDotSegments(
|
|
9633
|
-
let input =
|
|
9632
|
+
function removeDotSegments(path47) {
|
|
9633
|
+
let input = path47;
|
|
9634
9634
|
const output = [];
|
|
9635
9635
|
let nextSlash = -1;
|
|
9636
9636
|
let len = 0;
|
|
@@ -9882,8 +9882,8 @@ var require_schemes = __commonJS({
|
|
|
9882
9882
|
wsComponent.secure = void 0;
|
|
9883
9883
|
}
|
|
9884
9884
|
if (wsComponent.resourceName) {
|
|
9885
|
-
const [
|
|
9886
|
-
wsComponent.path =
|
|
9885
|
+
const [path47, query] = wsComponent.resourceName.split("?");
|
|
9886
|
+
wsComponent.path = path47 && path47 !== "/" ? path47 : void 0;
|
|
9887
9887
|
wsComponent.query = query;
|
|
9888
9888
|
wsComponent.resourceName = void 0;
|
|
9889
9889
|
}
|
|
@@ -10042,7 +10042,7 @@ var require_fast_uri = __commonJS({
|
|
|
10042
10042
|
}
|
|
10043
10043
|
return uri;
|
|
10044
10044
|
}
|
|
10045
|
-
function
|
|
10045
|
+
function resolve33(baseURI, relativeURI, options) {
|
|
10046
10046
|
const schemelessOptions = options ? Object.assign({ scheme: "null" }, options) : { scheme: "null" };
|
|
10047
10047
|
const resolved = resolveComponent(parse4(baseURI, schemelessOptions), parse4(relativeURI, schemelessOptions), schemelessOptions, true);
|
|
10048
10048
|
schemelessOptions.skipEscape = true;
|
|
@@ -10300,7 +10300,7 @@ var require_fast_uri = __commonJS({
|
|
|
10300
10300
|
var fastUri = {
|
|
10301
10301
|
SCHEMES,
|
|
10302
10302
|
normalize: normalize3,
|
|
10303
|
-
resolve:
|
|
10303
|
+
resolve: resolve33,
|
|
10304
10304
|
resolveComponent,
|
|
10305
10305
|
equal,
|
|
10306
10306
|
serialize,
|
|
@@ -13276,12 +13276,12 @@ var require_dist = __commonJS({
|
|
|
13276
13276
|
throw new Error(`Unknown format "${name}"`);
|
|
13277
13277
|
return f2;
|
|
13278
13278
|
};
|
|
13279
|
-
function addFormats(ajv, list,
|
|
13279
|
+
function addFormats(ajv, list, fs40, exportName) {
|
|
13280
13280
|
var _a3;
|
|
13281
13281
|
var _b2;
|
|
13282
13282
|
(_a3 = (_b2 = ajv.opts.code).formats) !== null && _a3 !== void 0 ? _a3 : _b2.formats = (0, codegen_1._)`require("ajv-formats/dist/formats").${exportName}`;
|
|
13283
13283
|
for (const f2 of list)
|
|
13284
|
-
ajv.addFormat(f2,
|
|
13284
|
+
ajv.addFormat(f2, fs40[f2]);
|
|
13285
13285
|
}
|
|
13286
13286
|
module2.exports = exports2 = formatsPlugin;
|
|
13287
13287
|
Object.defineProperty(exports2, "__esModule", { value: true });
|
|
@@ -14565,11 +14565,11 @@ var parser = new YargsParser({
|
|
|
14565
14565
|
resolve: import_path2.resolve,
|
|
14566
14566
|
// TODO: figure out a way to combine ESM and CJS coverage, such that
|
|
14567
14567
|
// we can exercise all the lines below:
|
|
14568
|
-
require: (
|
|
14568
|
+
require: (path47) => {
|
|
14569
14569
|
if (typeof require !== "undefined") {
|
|
14570
|
-
return require(
|
|
14571
|
-
} else if (
|
|
14572
|
-
return JSON.parse((0, import_fs2.readFileSync)(
|
|
14570
|
+
return require(path47);
|
|
14571
|
+
} else if (path47.match(/\.json$/)) {
|
|
14572
|
+
return JSON.parse((0, import_fs2.readFileSync)(path47, "utf8"));
|
|
14573
14573
|
} else {
|
|
14574
14574
|
throw Error("only .json config files are supported in ESM");
|
|
14575
14575
|
}
|
|
@@ -15912,13 +15912,13 @@ function usage(yargs, shim3) {
|
|
|
15912
15912
|
};
|
|
15913
15913
|
self.stringifiedValues = function stringifiedValues(values, separator) {
|
|
15914
15914
|
let string3 = "";
|
|
15915
|
-
const
|
|
15915
|
+
const sep5 = separator || ", ";
|
|
15916
15916
|
const array2 = [].concat(values);
|
|
15917
15917
|
if (!values || !array2.length)
|
|
15918
15918
|
return string3;
|
|
15919
15919
|
array2.forEach((value) => {
|
|
15920
15920
|
if (string3.length)
|
|
15921
|
-
string3 +=
|
|
15921
|
+
string3 += sep5;
|
|
15922
15922
|
string3 += JSON.stringify(value);
|
|
15923
15923
|
});
|
|
15924
15924
|
return string3;
|
|
@@ -17095,12 +17095,12 @@ var YargsInstance = class {
|
|
|
17095
17095
|
async getCompletion(args, done) {
|
|
17096
17096
|
argsert("<array> [function]", [args, done], arguments.length);
|
|
17097
17097
|
if (!done) {
|
|
17098
|
-
return new Promise((
|
|
17098
|
+
return new Promise((resolve33, reject) => {
|
|
17099
17099
|
__classPrivateFieldGet(this, _YargsInstance_completion, "f").getCompletion(args, (err, completions) => {
|
|
17100
17100
|
if (err)
|
|
17101
17101
|
reject(err);
|
|
17102
17102
|
else
|
|
17103
|
-
|
|
17103
|
+
resolve33(completions);
|
|
17104
17104
|
});
|
|
17105
17105
|
});
|
|
17106
17106
|
} else {
|
|
@@ -19250,8 +19250,8 @@ function getErrorMap() {
|
|
|
19250
19250
|
|
|
19251
19251
|
// node_modules/zod/v3/helpers/parseUtil.js
|
|
19252
19252
|
var makeIssue = (params) => {
|
|
19253
|
-
const { data, path:
|
|
19254
|
-
const fullPath = [...
|
|
19253
|
+
const { data, path: path47, errorMaps, issueData } = params;
|
|
19254
|
+
const fullPath = [...path47, ...issueData.path || []];
|
|
19255
19255
|
const fullIssue = {
|
|
19256
19256
|
...issueData,
|
|
19257
19257
|
path: fullPath
|
|
@@ -19367,11 +19367,11 @@ var errorUtil;
|
|
|
19367
19367
|
|
|
19368
19368
|
// node_modules/zod/v3/types.js
|
|
19369
19369
|
var ParseInputLazyPath = class {
|
|
19370
|
-
constructor(parent, value,
|
|
19370
|
+
constructor(parent, value, path47, key) {
|
|
19371
19371
|
this._cachedPath = [];
|
|
19372
19372
|
this.parent = parent;
|
|
19373
19373
|
this.data = value;
|
|
19374
|
-
this._path =
|
|
19374
|
+
this._path = path47;
|
|
19375
19375
|
this._key = key;
|
|
19376
19376
|
}
|
|
19377
19377
|
get path() {
|
|
@@ -23106,6 +23106,29 @@ var ValidationRecordSchema = external_exports.object({
|
|
|
23106
23106
|
var SyncValidationsInputSchema = external_exports.object({
|
|
23107
23107
|
validations: external_exports.array(ValidationRecordSchema)
|
|
23108
23108
|
});
|
|
23109
|
+
var AgentToolSchema = external_exports.enum(["claude_code", "codex"]);
|
|
23110
|
+
var UsageSourceSchema = external_exports.enum(["otel", "harvest"]);
|
|
23111
|
+
var AgentSessionUsageSchema = external_exports.object({
|
|
23112
|
+
sessionKey: external_exports.string().min(1),
|
|
23113
|
+
agentId: external_exports.number().int().min(HAR_AGENT_SLOT_MIN),
|
|
23114
|
+
agentTool: AgentToolSchema,
|
|
23115
|
+
workDir: external_exports.string().optional(),
|
|
23116
|
+
branch: external_exports.string().optional(),
|
|
23117
|
+
suffix: external_exports.string().optional(),
|
|
23118
|
+
tokensInput: external_exports.number().nonnegative().default(0),
|
|
23119
|
+
tokensOutput: external_exports.number().nonnegative().default(0),
|
|
23120
|
+
tokensCacheRead: external_exports.number().nonnegative().default(0),
|
|
23121
|
+
tokensCacheCreation: external_exports.number().nonnegative().default(0),
|
|
23122
|
+
tokensTotal: external_exports.number().nonnegative().default(0),
|
|
23123
|
+
costUsd: external_exports.number().nonnegative().nullable().optional(),
|
|
23124
|
+
modelBreakdown: external_exports.record(external_exports.unknown()).optional(),
|
|
23125
|
+
sources: external_exports.array(UsageSourceSchema).default([]),
|
|
23126
|
+
firstSeenAt: external_exports.string(),
|
|
23127
|
+
lastSeenAt: external_exports.string()
|
|
23128
|
+
});
|
|
23129
|
+
var SyncUsageInputSchema = external_exports.object({
|
|
23130
|
+
usage: external_exports.array(AgentSessionUsageSchema)
|
|
23131
|
+
});
|
|
23109
23132
|
|
|
23110
23133
|
// src/harness/manifest.ts
|
|
23111
23134
|
var GENERATOR_VERSION = "0.4.0";
|
|
@@ -23371,16 +23394,16 @@ async function handleAgentSkills(options) {
|
|
|
23371
23394
|
}
|
|
23372
23395
|
async function askYesNo(question) {
|
|
23373
23396
|
const rl = readline.createInterface({ input: process.stdin, output: process.stderr });
|
|
23374
|
-
return new Promise((
|
|
23397
|
+
return new Promise((resolve33) => {
|
|
23375
23398
|
process.stderr.write(`${question} `);
|
|
23376
23399
|
rl.once("line", (answer) => {
|
|
23377
23400
|
rl.close();
|
|
23378
23401
|
const trimmed = answer.trim();
|
|
23379
23402
|
if (trimmed === "") {
|
|
23380
|
-
|
|
23403
|
+
resolve33(true);
|
|
23381
23404
|
return;
|
|
23382
23405
|
}
|
|
23383
|
-
|
|
23406
|
+
resolve33(/^y(es)?$/i.test(trimmed));
|
|
23384
23407
|
});
|
|
23385
23408
|
});
|
|
23386
23409
|
}
|
|
@@ -23459,7 +23482,7 @@ function handleRemove(argv) {
|
|
|
23459
23482
|
}
|
|
23460
23483
|
|
|
23461
23484
|
// src/cli/commands/env.ts
|
|
23462
|
-
var
|
|
23485
|
+
var path40 = __toESM(require("path"));
|
|
23463
23486
|
|
|
23464
23487
|
// src/core/harness.ts
|
|
23465
23488
|
var fs19 = __toESM(require("fs"));
|
|
@@ -23965,13 +23988,13 @@ var MultipartBody = class {
|
|
|
23965
23988
|
// node_modules/@anthropic-ai/sdk/_shims/node-runtime.mjs
|
|
23966
23989
|
var import_web = require("node:stream/web");
|
|
23967
23990
|
var fileFromPathWarned = false;
|
|
23968
|
-
async function fileFromPath3(
|
|
23991
|
+
async function fileFromPath3(path47, ...args) {
|
|
23969
23992
|
const { fileFromPath: _fileFromPath } = await Promise.resolve().then(() => (init_fileFromPath(), fileFromPath_exports));
|
|
23970
23993
|
if (!fileFromPathWarned) {
|
|
23971
|
-
console.warn(`fileFromPath is deprecated; use fs.createReadStream(${JSON.stringify(
|
|
23994
|
+
console.warn(`fileFromPath is deprecated; use fs.createReadStream(${JSON.stringify(path47)}) instead`);
|
|
23972
23995
|
fileFromPathWarned = true;
|
|
23973
23996
|
}
|
|
23974
|
-
return await _fileFromPath(
|
|
23997
|
+
return await _fileFromPath(path47, ...args);
|
|
23975
23998
|
}
|
|
23976
23999
|
var defaultHttpAgent = new import_agentkeepalive.default({ keepAlive: true, timeout: 5 * 60 * 1e3 });
|
|
23977
24000
|
var defaultHttpsAgent = new import_agentkeepalive.default.HttpsAgent({ keepAlive: true, timeout: 5 * 60 * 1e3 });
|
|
@@ -24467,8 +24490,8 @@ async function defaultParseResponse(props) {
|
|
|
24467
24490
|
}
|
|
24468
24491
|
var APIPromise = class _APIPromise extends Promise {
|
|
24469
24492
|
constructor(responsePromise, parseResponse = defaultParseResponse) {
|
|
24470
|
-
super((
|
|
24471
|
-
|
|
24493
|
+
super((resolve33) => {
|
|
24494
|
+
resolve33(null);
|
|
24472
24495
|
});
|
|
24473
24496
|
this.responsePromise = responsePromise;
|
|
24474
24497
|
this.parseResponse = parseResponse;
|
|
@@ -24568,29 +24591,29 @@ var APIClient = class {
|
|
|
24568
24591
|
defaultIdempotencyKey() {
|
|
24569
24592
|
return `stainless-node-retry-${uuid4()}`;
|
|
24570
24593
|
}
|
|
24571
|
-
get(
|
|
24572
|
-
return this.methodRequest("get",
|
|
24594
|
+
get(path47, opts) {
|
|
24595
|
+
return this.methodRequest("get", path47, opts);
|
|
24573
24596
|
}
|
|
24574
|
-
post(
|
|
24575
|
-
return this.methodRequest("post",
|
|
24597
|
+
post(path47, opts) {
|
|
24598
|
+
return this.methodRequest("post", path47, opts);
|
|
24576
24599
|
}
|
|
24577
|
-
patch(
|
|
24578
|
-
return this.methodRequest("patch",
|
|
24600
|
+
patch(path47, opts) {
|
|
24601
|
+
return this.methodRequest("patch", path47, opts);
|
|
24579
24602
|
}
|
|
24580
|
-
put(
|
|
24581
|
-
return this.methodRequest("put",
|
|
24603
|
+
put(path47, opts) {
|
|
24604
|
+
return this.methodRequest("put", path47, opts);
|
|
24582
24605
|
}
|
|
24583
|
-
delete(
|
|
24584
|
-
return this.methodRequest("delete",
|
|
24606
|
+
delete(path47, opts) {
|
|
24607
|
+
return this.methodRequest("delete", path47, opts);
|
|
24585
24608
|
}
|
|
24586
|
-
methodRequest(method,
|
|
24609
|
+
methodRequest(method, path47, opts) {
|
|
24587
24610
|
return this.request(Promise.resolve(opts).then(async (opts2) => {
|
|
24588
24611
|
const body = opts2 && isBlobLike(opts2?.body) ? new DataView(await opts2.body.arrayBuffer()) : opts2?.body instanceof DataView ? opts2.body : opts2?.body instanceof ArrayBuffer ? new DataView(opts2.body) : opts2 && ArrayBuffer.isView(opts2?.body) ? new DataView(opts2.body.buffer) : opts2?.body;
|
|
24589
|
-
return { method, path:
|
|
24612
|
+
return { method, path: path47, ...opts2, body };
|
|
24590
24613
|
}));
|
|
24591
24614
|
}
|
|
24592
|
-
getAPIList(
|
|
24593
|
-
return this.requestAPIList(Page, { method: "get", path:
|
|
24615
|
+
getAPIList(path47, Page, opts) {
|
|
24616
|
+
return this.requestAPIList(Page, { method: "get", path: path47, ...opts });
|
|
24594
24617
|
}
|
|
24595
24618
|
calculateContentLength(body) {
|
|
24596
24619
|
if (typeof body === "string") {
|
|
@@ -24608,10 +24631,10 @@ var APIClient = class {
|
|
|
24608
24631
|
return null;
|
|
24609
24632
|
}
|
|
24610
24633
|
buildRequest(options) {
|
|
24611
|
-
const { method, path:
|
|
24634
|
+
const { method, path: path47, query, headers = {} } = options;
|
|
24612
24635
|
const body = ArrayBuffer.isView(options.body) || options.__binaryRequest && typeof options.body === "string" ? options.body : isMultipartBody(options.body) ? options.body.body : options.body ? JSON.stringify(options.body, null, 2) : null;
|
|
24613
24636
|
const contentLength = this.calculateContentLength(body);
|
|
24614
|
-
const url = this.buildURL(
|
|
24637
|
+
const url = this.buildURL(path47, query);
|
|
24615
24638
|
if ("timeout" in options)
|
|
24616
24639
|
validatePositiveInteger("timeout", options.timeout);
|
|
24617
24640
|
const timeout = options.timeout ?? this.timeout;
|
|
@@ -24720,8 +24743,8 @@ var APIClient = class {
|
|
|
24720
24743
|
const request = this.makeRequest(options, null);
|
|
24721
24744
|
return new PagePromise(this, request, Page);
|
|
24722
24745
|
}
|
|
24723
|
-
buildURL(
|
|
24724
|
-
const url = isAbsoluteURL(
|
|
24746
|
+
buildURL(path47, query) {
|
|
24747
|
+
const url = isAbsoluteURL(path47) ? new URL(path47) : new URL(this.baseURL + (this.baseURL.endsWith("/") && path47.startsWith("/") ? path47.slice(1) : path47));
|
|
24725
24748
|
const defaultQuery = this.defaultQuery();
|
|
24726
24749
|
if (!isEmptyObj(defaultQuery)) {
|
|
24727
24750
|
query = { ...defaultQuery, ...query };
|
|
@@ -25006,7 +25029,7 @@ var startsWithSchemeRegexp = new RegExp("^(?:[a-z]+:)?//", "i");
|
|
|
25006
25029
|
var isAbsoluteURL = (url) => {
|
|
25007
25030
|
return startsWithSchemeRegexp.test(url);
|
|
25008
25031
|
};
|
|
25009
|
-
var sleep = (ms) => new Promise((
|
|
25032
|
+
var sleep = (ms) => new Promise((resolve33) => setTimeout(resolve33, ms));
|
|
25010
25033
|
var validatePositiveInteger = (name, n2) => {
|
|
25011
25034
|
if (typeof n2 !== "number" || !Number.isInteger(n2)) {
|
|
25012
25035
|
throw new AnthropicError(`${name} must be an integer`);
|
|
@@ -25497,12 +25520,12 @@ var PromptCachingBetaMessageStream = class _PromptCachingBetaMessageStream {
|
|
|
25497
25520
|
}
|
|
25498
25521
|
return this._emit("error", new AnthropicError(String(error3)));
|
|
25499
25522
|
});
|
|
25500
|
-
__classPrivateFieldSet7(this, _PromptCachingBetaMessageStream_connectedPromise, new Promise((
|
|
25501
|
-
__classPrivateFieldSet7(this, _PromptCachingBetaMessageStream_resolveConnectedPromise,
|
|
25523
|
+
__classPrivateFieldSet7(this, _PromptCachingBetaMessageStream_connectedPromise, new Promise((resolve33, reject) => {
|
|
25524
|
+
__classPrivateFieldSet7(this, _PromptCachingBetaMessageStream_resolveConnectedPromise, resolve33, "f");
|
|
25502
25525
|
__classPrivateFieldSet7(this, _PromptCachingBetaMessageStream_rejectConnectedPromise, reject, "f");
|
|
25503
25526
|
}), "f");
|
|
25504
|
-
__classPrivateFieldSet7(this, _PromptCachingBetaMessageStream_endPromise, new Promise((
|
|
25505
|
-
__classPrivateFieldSet7(this, _PromptCachingBetaMessageStream_resolveEndPromise,
|
|
25527
|
+
__classPrivateFieldSet7(this, _PromptCachingBetaMessageStream_endPromise, new Promise((resolve33, reject) => {
|
|
25528
|
+
__classPrivateFieldSet7(this, _PromptCachingBetaMessageStream_resolveEndPromise, resolve33, "f");
|
|
25506
25529
|
__classPrivateFieldSet7(this, _PromptCachingBetaMessageStream_rejectEndPromise, reject, "f");
|
|
25507
25530
|
}), "f");
|
|
25508
25531
|
__classPrivateFieldGet8(this, _PromptCachingBetaMessageStream_connectedPromise, "f").catch(() => {
|
|
@@ -25631,11 +25654,11 @@ var PromptCachingBetaMessageStream = class _PromptCachingBetaMessageStream {
|
|
|
25631
25654
|
* const message = await stream.emitted('message') // rejects if the stream errors
|
|
25632
25655
|
*/
|
|
25633
25656
|
emitted(event) {
|
|
25634
|
-
return new Promise((
|
|
25657
|
+
return new Promise((resolve33, reject) => {
|
|
25635
25658
|
__classPrivateFieldSet7(this, _PromptCachingBetaMessageStream_catchingPromiseCreated, true, "f");
|
|
25636
25659
|
if (event !== "error")
|
|
25637
25660
|
this.once("error", reject);
|
|
25638
|
-
this.once(event,
|
|
25661
|
+
this.once(event, resolve33);
|
|
25639
25662
|
});
|
|
25640
25663
|
}
|
|
25641
25664
|
async done() {
|
|
@@ -25862,7 +25885,7 @@ var PromptCachingBetaMessageStream = class _PromptCachingBetaMessageStream {
|
|
|
25862
25885
|
if (done) {
|
|
25863
25886
|
return { value: void 0, done: true };
|
|
25864
25887
|
}
|
|
25865
|
-
return new Promise((
|
|
25888
|
+
return new Promise((resolve33, reject) => readQueue.push({ resolve: resolve33, reject })).then((chunk2) => chunk2 ? { value: chunk2, done: false } : { value: void 0, done: true });
|
|
25866
25889
|
}
|
|
25867
25890
|
const chunk = pushQueue.shift();
|
|
25868
25891
|
return { value: chunk, done: false };
|
|
@@ -26010,12 +26033,12 @@ var MessageStream = class _MessageStream {
|
|
|
26010
26033
|
}
|
|
26011
26034
|
return this._emit("error", new AnthropicError(String(error3)));
|
|
26012
26035
|
});
|
|
26013
|
-
__classPrivateFieldSet8(this, _MessageStream_connectedPromise, new Promise((
|
|
26014
|
-
__classPrivateFieldSet8(this, _MessageStream_resolveConnectedPromise,
|
|
26036
|
+
__classPrivateFieldSet8(this, _MessageStream_connectedPromise, new Promise((resolve33, reject) => {
|
|
26037
|
+
__classPrivateFieldSet8(this, _MessageStream_resolveConnectedPromise, resolve33, "f");
|
|
26015
26038
|
__classPrivateFieldSet8(this, _MessageStream_rejectConnectedPromise, reject, "f");
|
|
26016
26039
|
}), "f");
|
|
26017
|
-
__classPrivateFieldSet8(this, _MessageStream_endPromise, new Promise((
|
|
26018
|
-
__classPrivateFieldSet8(this, _MessageStream_resolveEndPromise,
|
|
26040
|
+
__classPrivateFieldSet8(this, _MessageStream_endPromise, new Promise((resolve33, reject) => {
|
|
26041
|
+
__classPrivateFieldSet8(this, _MessageStream_resolveEndPromise, resolve33, "f");
|
|
26019
26042
|
__classPrivateFieldSet8(this, _MessageStream_rejectEndPromise, reject, "f");
|
|
26020
26043
|
}), "f");
|
|
26021
26044
|
__classPrivateFieldGet9(this, _MessageStream_connectedPromise, "f").catch(() => {
|
|
@@ -26144,11 +26167,11 @@ var MessageStream = class _MessageStream {
|
|
|
26144
26167
|
* const message = await stream.emitted('message') // rejects if the stream errors
|
|
26145
26168
|
*/
|
|
26146
26169
|
emitted(event) {
|
|
26147
|
-
return new Promise((
|
|
26170
|
+
return new Promise((resolve33, reject) => {
|
|
26148
26171
|
__classPrivateFieldSet8(this, _MessageStream_catchingPromiseCreated, true, "f");
|
|
26149
26172
|
if (event !== "error")
|
|
26150
26173
|
this.once("error", reject);
|
|
26151
|
-
this.once(event,
|
|
26174
|
+
this.once(event, resolve33);
|
|
26152
26175
|
});
|
|
26153
26176
|
}
|
|
26154
26177
|
async done() {
|
|
@@ -26375,7 +26398,7 @@ var MessageStream = class _MessageStream {
|
|
|
26375
26398
|
if (done) {
|
|
26376
26399
|
return { value: void 0, done: true };
|
|
26377
26400
|
}
|
|
26378
|
-
return new Promise((
|
|
26401
|
+
return new Promise((resolve33, reject) => readQueue.push({ resolve: resolve33, reject })).then((chunk2) => chunk2 ? { value: chunk2, done: false } : { value: void 0, done: true });
|
|
26379
26402
|
}
|
|
26380
26403
|
const chunk = pushQueue.shift();
|
|
26381
26404
|
return { value: chunk, done: false };
|
|
@@ -26635,11 +26658,11 @@ async function promptApplyAgentMdProposal(repoPath) {
|
|
|
26635
26658
|
}
|
|
26636
26659
|
async function askYesNo2(question) {
|
|
26637
26660
|
const rl = readline2.createInterface({ input: process.stdin, output: process.stderr });
|
|
26638
|
-
return new Promise((
|
|
26661
|
+
return new Promise((resolve33) => {
|
|
26639
26662
|
process.stderr.write(`${question} `);
|
|
26640
26663
|
rl.once("line", (answer) => {
|
|
26641
26664
|
rl.close();
|
|
26642
|
-
|
|
26665
|
+
resolve33(/^y(es)?$/i.test(answer.trim()));
|
|
26643
26666
|
});
|
|
26644
26667
|
});
|
|
26645
26668
|
}
|
|
@@ -26869,7 +26892,7 @@ async function executeAuthoringToolCall(toolName, input, repoPath, harnessDir, c
|
|
|
26869
26892
|
}
|
|
26870
26893
|
async function askUser(question, options) {
|
|
26871
26894
|
const rl = readline3.createInterface({ input: process.stdin, output: process.stderr });
|
|
26872
|
-
return new Promise((
|
|
26895
|
+
return new Promise((resolve33) => {
|
|
26873
26896
|
process.stderr.write("\n");
|
|
26874
26897
|
process.stderr.write(`\u2753 ${question}
|
|
26875
26898
|
`);
|
|
@@ -26880,7 +26903,7 @@ async function askUser(question, options) {
|
|
|
26880
26903
|
process.stderr.write("> ");
|
|
26881
26904
|
rl.once("line", (answer) => {
|
|
26882
26905
|
rl.close();
|
|
26883
|
-
|
|
26906
|
+
resolve33(answer.trim());
|
|
26884
26907
|
});
|
|
26885
26908
|
});
|
|
26886
26909
|
}
|
|
@@ -27024,7 +27047,7 @@ function runScript(scriptPath, args = [], options = {}) {
|
|
|
27024
27047
|
function runScriptCapture(scriptPath, args = [], options = {}) {
|
|
27025
27048
|
const stream = options.stream ?? false;
|
|
27026
27049
|
const { stream: _stream, ...spawnOptions } = options;
|
|
27027
|
-
return new Promise((
|
|
27050
|
+
return new Promise((resolve33) => {
|
|
27028
27051
|
const proc = (0, import_child_process.spawn)("bash", [scriptPath, ...args], {
|
|
27029
27052
|
...spawnOptions,
|
|
27030
27053
|
stdio: ["inherit", "pipe", "pipe"]
|
|
@@ -27039,13 +27062,13 @@ function runScriptCapture(scriptPath, args = [], options = {}) {
|
|
|
27039
27062
|
stderr += d2;
|
|
27040
27063
|
if (stream) process.stderr.write(d2);
|
|
27041
27064
|
});
|
|
27042
|
-
proc.on("close", (code) =>
|
|
27065
|
+
proc.on("close", (code) => resolve33({ stdout, stderr, code: code ?? 0 }));
|
|
27043
27066
|
});
|
|
27044
27067
|
}
|
|
27045
27068
|
function runShellCommand(command2, options = {}) {
|
|
27046
27069
|
const stream = options.stream ?? false;
|
|
27047
27070
|
const { stream: _stream, ...spawnOptions } = options;
|
|
27048
|
-
return new Promise((
|
|
27071
|
+
return new Promise((resolve33) => {
|
|
27049
27072
|
const proc = (0, import_child_process.spawn)("bash", ["-lc", command2], {
|
|
27050
27073
|
...spawnOptions,
|
|
27051
27074
|
stdio: ["inherit", "pipe", "pipe"]
|
|
@@ -27060,7 +27083,7 @@ function runShellCommand(command2, options = {}) {
|
|
|
27060
27083
|
stderr += d2;
|
|
27061
27084
|
if (stream) process.stderr.write(d2);
|
|
27062
27085
|
});
|
|
27063
|
-
proc.on("close", (code) =>
|
|
27086
|
+
proc.on("close", (code) => resolve33({ stdout, stderr, code: code ?? 0 }));
|
|
27064
27087
|
});
|
|
27065
27088
|
}
|
|
27066
27089
|
|
|
@@ -27849,6 +27872,24 @@ function harnessExists(repoPath) {
|
|
|
27849
27872
|
}
|
|
27850
27873
|
|
|
27851
27874
|
// src/harness/stage-templates.ts
|
|
27875
|
+
var STAGE_TEMPLATE_IDS = ["playwright", "rocketsim"];
|
|
27876
|
+
var TemplateManifestFileSchema = external_exports.object({
|
|
27877
|
+
src: external_exports.string().min(1),
|
|
27878
|
+
dest: external_exports.string().min(1),
|
|
27879
|
+
executable: external_exports.boolean().optional(),
|
|
27880
|
+
skipFlag: external_exports.string().optional()
|
|
27881
|
+
});
|
|
27882
|
+
var StageTemplateManifestSchema = external_exports.object({
|
|
27883
|
+
id: external_exports.enum(STAGE_TEMPLATE_IDS),
|
|
27884
|
+
stageId: external_exports.string().min(1),
|
|
27885
|
+
verificationStages: external_exports.array(external_exports.string().min(1)).min(1),
|
|
27886
|
+
stage: external_exports.record(external_exports.unknown()),
|
|
27887
|
+
files: external_exports.array(TemplateManifestFileSchema).min(1),
|
|
27888
|
+
optionalFiles: external_exports.array(TemplateManifestFileSchema).optional(),
|
|
27889
|
+
merge: external_exports.record(external_exports.string()).optional(),
|
|
27890
|
+
nextSteps: external_exports.array(external_exports.string().min(1)).min(1),
|
|
27891
|
+
docsPath: external_exports.string().min(1)
|
|
27892
|
+
});
|
|
27852
27893
|
function resolveTemplateDir(templateId) {
|
|
27853
27894
|
const dir = path17.join(resolveTemplatesDir(), "stage-templates", templateId);
|
|
27854
27895
|
if (!fs18.existsSync(dir)) {
|
|
@@ -27858,11 +27899,16 @@ function resolveTemplateDir(templateId) {
|
|
|
27858
27899
|
}
|
|
27859
27900
|
function readTemplateManifest(templateId) {
|
|
27860
27901
|
const manifestPath = path17.join(resolveTemplateDir(templateId), "template.manifest.json");
|
|
27861
|
-
const
|
|
27862
|
-
|
|
27863
|
-
|
|
27902
|
+
const parsed = StageTemplateManifestSchema.safeParse(
|
|
27903
|
+
JSON.parse(fs18.readFileSync(manifestPath, "utf8"))
|
|
27904
|
+
);
|
|
27905
|
+
if (!parsed.success) {
|
|
27906
|
+
throw new Error(`Invalid template manifest for ${templateId}: ${parsed.error.message}`);
|
|
27907
|
+
}
|
|
27908
|
+
if (parsed.data.id !== templateId) {
|
|
27909
|
+
throw new Error(`Template manifest id mismatch: expected ${templateId}, got ${parsed.data.id}`);
|
|
27864
27910
|
}
|
|
27865
|
-
return
|
|
27911
|
+
return parsed.data;
|
|
27866
27912
|
}
|
|
27867
27913
|
function ensureParentDir(filePath) {
|
|
27868
27914
|
const parent = path17.dirname(filePath);
|
|
@@ -27891,7 +27937,7 @@ function copyTemplateFile(templateDir, file, repoPath, force) {
|
|
|
27891
27937
|
function mergePackageJson(repoPath, templateDir, fragmentRelPath, warnings) {
|
|
27892
27938
|
const packagePath = path17.join(repoPath, "package.json");
|
|
27893
27939
|
if (!fs18.existsSync(packagePath)) {
|
|
27894
|
-
throw new Error("No package.json in repo root. Add one before applying
|
|
27940
|
+
throw new Error("No package.json in repo root. Add one before applying this stage template.");
|
|
27895
27941
|
}
|
|
27896
27942
|
const fragmentPath = path17.join(templateDir, fragmentRelPath);
|
|
27897
27943
|
if (!fs18.existsSync(fragmentPath)) {
|
|
@@ -27924,8 +27970,7 @@ function patchStageRegistry(repoPath, manifest, force) {
|
|
|
27924
27970
|
}
|
|
27925
27971
|
const stages = existing ? registry2.stages.map((s2) => s2.id === stage.id ? stage : s2) : [...registry2.stages, stage];
|
|
27926
27972
|
const verificationStages = [...registry2.verificationStages ?? []];
|
|
27927
|
-
const
|
|
27928
|
-
for (const id of toAdd) {
|
|
27973
|
+
for (const id of manifest.verificationStages) {
|
|
27929
27974
|
if (!verificationStages.includes(id)) {
|
|
27930
27975
|
verificationStages.push(id);
|
|
27931
27976
|
}
|
|
@@ -27934,7 +27979,7 @@ function patchStageRegistry(repoPath, manifest, force) {
|
|
|
27934
27979
|
if (verifyIdx >= 0) {
|
|
27935
27980
|
stages[verifyIdx] = {
|
|
27936
27981
|
...stages[verifyIdx],
|
|
27937
|
-
description:
|
|
27982
|
+
description: `Verification pipeline (quick smoke by default; --full runs the registry's verificationStages: ${verificationStages.join(", ")})`,
|
|
27938
27983
|
acceptsArgs: ["--full"]
|
|
27939
27984
|
};
|
|
27940
27985
|
}
|
|
@@ -28010,21 +28055,22 @@ function applyStageTemplate(repoPath, templateId, options = {}) {
|
|
|
28010
28055
|
for (const warning of warnings) {
|
|
28011
28056
|
warn(` \u26A0 ${warning}`);
|
|
28012
28057
|
}
|
|
28013
|
-
const nextSteps = manifest.nextSteps ?? [
|
|
28014
|
-
"npm install",
|
|
28015
|
-
"npx playwright install",
|
|
28016
|
-
"./.har/launch.sh 1",
|
|
28017
|
-
`./.har/stages/${manifest.stageId}.sh 1`,
|
|
28018
|
-
"npx playwright show-report .har/artifacts/browser-e2e/playwright-report"
|
|
28019
|
-
];
|
|
28020
28058
|
return {
|
|
28021
28059
|
templateId,
|
|
28022
28060
|
stageId: manifest.stageId,
|
|
28023
28061
|
filesWritten,
|
|
28024
28062
|
warnings,
|
|
28025
|
-
nextSteps
|
|
28063
|
+
nextSteps: manifest.nextSteps,
|
|
28064
|
+
docsPath: manifest.docsPath
|
|
28026
28065
|
};
|
|
28027
28066
|
}
|
|
28067
|
+
function listStageTemplateIds() {
|
|
28068
|
+
const root = path17.join(resolveTemplatesDir(), "stage-templates");
|
|
28069
|
+
if (!fs18.existsSync(root)) return [...STAGE_TEMPLATE_IDS];
|
|
28070
|
+
return fs18.readdirSync(root, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => entry.name).filter(
|
|
28071
|
+
(name) => STAGE_TEMPLATE_IDS.includes(name)
|
|
28072
|
+
);
|
|
28073
|
+
}
|
|
28028
28074
|
|
|
28029
28075
|
// src/utils/validation.ts
|
|
28030
28076
|
function requireApiKey() {
|
|
@@ -28156,9 +28202,111 @@ function addStageTemplate(repoPath, templateId, options = {}) {
|
|
|
28156
28202
|
return applyStageTemplate(repoPath, templateId, options);
|
|
28157
28203
|
}
|
|
28158
28204
|
|
|
28159
|
-
// src/harness/
|
|
28205
|
+
// src/harness/custom-stage.ts
|
|
28160
28206
|
var fs20 = __toESM(require("fs"));
|
|
28161
28207
|
var path19 = __toESM(require("path"));
|
|
28208
|
+
var STAGE_ID_PATTERN = /^[a-z0-9][a-z0-9._-]*$/;
|
|
28209
|
+
function addCustomStage(repoPath, options) {
|
|
28210
|
+
const resolved = path19.resolve(repoPath);
|
|
28211
|
+
if (!harnessExists(resolved)) {
|
|
28212
|
+
throw new Error('No .har/ harness found. Run "har env init" first.');
|
|
28213
|
+
}
|
|
28214
|
+
const id = options.id.trim();
|
|
28215
|
+
if (!STAGE_ID_PATTERN.test(id)) {
|
|
28216
|
+
throw new Error(
|
|
28217
|
+
`Invalid stage id "${id}". Use lowercase letters, digits, dots, dashes (e.g. "unit-tests").`
|
|
28218
|
+
);
|
|
28219
|
+
}
|
|
28220
|
+
const kind2 = options.kind ?? "custom";
|
|
28221
|
+
if (!HarnessStageKindSchema.options.includes(kind2)) {
|
|
28222
|
+
throw new Error(
|
|
28223
|
+
`Invalid stage kind "${kind2}". Available: ${HarnessStageKindSchema.options.join(", ")}`
|
|
28224
|
+
);
|
|
28225
|
+
}
|
|
28226
|
+
if (Boolean(options.command) === Boolean(options.script)) {
|
|
28227
|
+
throw new Error(
|
|
28228
|
+
'Choose exactly one execution mode: --command "<shell command>" for a one-liner, or --script to scaffold .har/stages/<id>.sh.'
|
|
28229
|
+
);
|
|
28230
|
+
}
|
|
28231
|
+
const registry2 = readStageRegistry(resolved);
|
|
28232
|
+
const existing = registry2.stages.find((s2) => s2.id === id);
|
|
28233
|
+
if (existing && !options.force) {
|
|
28234
|
+
throw new Error(`Stage "${id}" already registered in .har/stages.json. Use --force to replace.`);
|
|
28235
|
+
}
|
|
28236
|
+
const filesWritten = [];
|
|
28237
|
+
const stage = {
|
|
28238
|
+
id,
|
|
28239
|
+
kind: kind2,
|
|
28240
|
+
description: options.description ?? `Custom ${kind2} stage`,
|
|
28241
|
+
requiresAgentId: true,
|
|
28242
|
+
artifacts: []
|
|
28243
|
+
};
|
|
28244
|
+
if (options.command) {
|
|
28245
|
+
stage.command = options.command;
|
|
28246
|
+
} else {
|
|
28247
|
+
const scriptRel = path19.join("stages", `${id}.sh`);
|
|
28248
|
+
const scriptAbs = path19.join(resolved, ".har", scriptRel);
|
|
28249
|
+
if (fs20.existsSync(scriptAbs) && !options.force) {
|
|
28250
|
+
throw new Error(`Stage script already exists: .har/${scriptRel}. Use --force to overwrite.`);
|
|
28251
|
+
}
|
|
28252
|
+
const skeletonPath = path19.join(
|
|
28253
|
+
resolveTemplatesDir(),
|
|
28254
|
+
"stage-templates",
|
|
28255
|
+
"custom-stage-skeleton.sh"
|
|
28256
|
+
);
|
|
28257
|
+
const skeleton = fs20.readFileSync(skeletonPath, "utf8").replace(/__STAGE_ID__/g, id).replace(/__STAGE_KIND__/g, kind2).replace(/__STAGE_DESCRIPTION__/g, stage.description);
|
|
28258
|
+
fs20.mkdirSync(path19.dirname(scriptAbs), { recursive: true });
|
|
28259
|
+
fs20.writeFileSync(scriptAbs, skeleton);
|
|
28260
|
+
fs20.chmodSync(scriptAbs, 493);
|
|
28261
|
+
filesWritten.push(`.har/${scriptRel}`);
|
|
28262
|
+
stage.script = scriptRel;
|
|
28263
|
+
stage.artifacts = [
|
|
28264
|
+
{
|
|
28265
|
+
path: `.har/artifacts/${id}`,
|
|
28266
|
+
kind: "directory",
|
|
28267
|
+
description: `Artifacts for the ${id} stage`
|
|
28268
|
+
}
|
|
28269
|
+
];
|
|
28270
|
+
}
|
|
28271
|
+
const parsedStage = HarnessStageSchema.parse(stage);
|
|
28272
|
+
const stages = existing ? registry2.stages.map((s2) => s2.id === id ? parsedStage : s2) : [...registry2.stages, parsedStage];
|
|
28273
|
+
const verification = options.verification ?? false;
|
|
28274
|
+
const verificationStages = [...registry2.verificationStages ?? []];
|
|
28275
|
+
if (verification && !verificationStages.includes(id)) {
|
|
28276
|
+
verificationStages.push(id);
|
|
28277
|
+
const verifyIdx = stages.findIndex((s2) => s2.id === "verify");
|
|
28278
|
+
if (verifyIdx >= 0) {
|
|
28279
|
+
stages[verifyIdx] = {
|
|
28280
|
+
...stages[verifyIdx],
|
|
28281
|
+
description: `Verification pipeline (quick smoke by default; --full runs the registry's verificationStages: ${verificationStages.join(", ")})`
|
|
28282
|
+
};
|
|
28283
|
+
}
|
|
28284
|
+
}
|
|
28285
|
+
writeStageRegistry(resolved, { ...registry2, stages, verificationStages });
|
|
28286
|
+
filesWritten.push(".har/stages.json");
|
|
28287
|
+
const nextSteps = options.command ? [
|
|
28288
|
+
`Try it: ${options.command.replace(/\{agentId\}/g, "1")}`,
|
|
28289
|
+
`Agents run it via the MCP tool har_run_stage (stageId: "${id}")`,
|
|
28290
|
+
...verification ? ["./.har/verify.sh 1 --full # runs it as part of full verification"] : []
|
|
28291
|
+
] : [
|
|
28292
|
+
`Edit .har/stages/${id}.sh \u2014 replace the TODO block with the real check`,
|
|
28293
|
+
"./.har/launch.sh 1",
|
|
28294
|
+
`./.har/stages/${id}.sh 1`,
|
|
28295
|
+
...verification ? ["./.har/verify.sh 1 --full # runs it as part of full verification"] : []
|
|
28296
|
+
];
|
|
28297
|
+
return {
|
|
28298
|
+
stageId: id,
|
|
28299
|
+
kind: kind2,
|
|
28300
|
+
mode: options.command ? "command" : "script",
|
|
28301
|
+
filesWritten,
|
|
28302
|
+
verification,
|
|
28303
|
+
nextSteps
|
|
28304
|
+
};
|
|
28305
|
+
}
|
|
28306
|
+
|
|
28307
|
+
// src/harness/adaptation-prompt.ts
|
|
28308
|
+
var fs21 = __toESM(require("fs"));
|
|
28309
|
+
var path20 = __toESM(require("path"));
|
|
28162
28310
|
var ADAPTATION_PROMPT_FILE = "ADAPT-PROMPT.md";
|
|
28163
28311
|
var PROFILE_HINTS = {
|
|
28164
28312
|
default: "Web app profile (SaaS/full-stack) \u2014 Docker Compose for shared infra (HARNESS_INFRA_SERVICES), PM2 for the primary application only, git worktree per agent slot by default. Launch provisions toolchain via harness.env (HARNESS_ECOSYSTEM, HARNESS_INSTALL_CMD) and writes paths to .env.agent.<id>. Identify the primary app agents modify; run supporting services shared.",
|
|
@@ -28170,7 +28318,7 @@ function loadTemplate(name) {
|
|
|
28170
28318
|
if (!filePath) {
|
|
28171
28319
|
throw new Error(`Adaptation prompt template not found: ${name}. Run npm run build.`);
|
|
28172
28320
|
}
|
|
28173
|
-
return
|
|
28321
|
+
return fs21.readFileSync(filePath, "utf8");
|
|
28174
28322
|
}
|
|
28175
28323
|
function applyProfilePlaceholders(content, profile) {
|
|
28176
28324
|
return content.replace(/\{\{PROFILE\}\}/g, profile).replace(/\{\{PROFILE_HINT\}\}/g, PROFILE_HINTS[profile]);
|
|
@@ -28192,7 +28340,7 @@ function buildMaintainAdaptationPrompt(_repoPath, bundleReport) {
|
|
|
28192
28340
|
}
|
|
28193
28341
|
function writeAdaptationPrompt(repoPath, content) {
|
|
28194
28342
|
const harnessDir = getHarnessDir(repoPath);
|
|
28195
|
-
const filePath =
|
|
28343
|
+
const filePath = path20.join(harnessDir, ADAPTATION_PROMPT_FILE);
|
|
28196
28344
|
writeFileSafe(filePath, content);
|
|
28197
28345
|
return filePath;
|
|
28198
28346
|
}
|
|
@@ -28211,25 +28359,25 @@ function printAdaptationPrompt(content) {
|
|
|
28211
28359
|
}
|
|
28212
28360
|
|
|
28213
28361
|
// src/harness/cursor-rule.ts
|
|
28214
|
-
var
|
|
28215
|
-
var
|
|
28362
|
+
var fs22 = __toESM(require("fs"));
|
|
28363
|
+
var path21 = __toESM(require("path"));
|
|
28216
28364
|
var readline4 = __toESM(require("readline"));
|
|
28217
28365
|
var CURSOR_RULE_RELATIVE_PATH = ".cursor/rules/har-workflow.mdc";
|
|
28218
28366
|
function getCursorRulePath(repoPath) {
|
|
28219
|
-
return
|
|
28367
|
+
return path21.join(repoPath, CURSOR_RULE_RELATIVE_PATH);
|
|
28220
28368
|
}
|
|
28221
28369
|
function isCursorWorkspace(repoPath) {
|
|
28222
|
-
return
|
|
28370
|
+
return fs22.existsSync(path21.join(repoPath, ".cursor"));
|
|
28223
28371
|
}
|
|
28224
28372
|
function cursorRuleExists(repoPath) {
|
|
28225
|
-
return
|
|
28373
|
+
return fs22.existsSync(getCursorRulePath(repoPath));
|
|
28226
28374
|
}
|
|
28227
28375
|
function scaffoldCursorRule(repoPath) {
|
|
28228
28376
|
const templatePath = resolveTemplateFile("cursor-rule.mdc.template");
|
|
28229
28377
|
if (!templatePath) {
|
|
28230
28378
|
throw new Error("Cursor rule template not found (cursor-rule.mdc.template). Run npm run build.");
|
|
28231
28379
|
}
|
|
28232
|
-
const content =
|
|
28380
|
+
const content = fs22.readFileSync(templatePath, "utf8");
|
|
28233
28381
|
writeFileSafe(getCursorRulePath(repoPath), content);
|
|
28234
28382
|
}
|
|
28235
28383
|
async function handleCursorRule(options) {
|
|
@@ -28267,16 +28415,16 @@ async function promptScaffoldCursorRule(exists) {
|
|
|
28267
28415
|
}
|
|
28268
28416
|
async function askYesNo3(question) {
|
|
28269
28417
|
const rl = readline4.createInterface({ input: process.stdin, output: process.stderr });
|
|
28270
|
-
return new Promise((
|
|
28418
|
+
return new Promise((resolve33) => {
|
|
28271
28419
|
process.stderr.write(`${question} `);
|
|
28272
28420
|
rl.once("line", (answer) => {
|
|
28273
28421
|
rl.close();
|
|
28274
28422
|
const trimmed = answer.trim();
|
|
28275
28423
|
if (trimmed === "") {
|
|
28276
|
-
|
|
28424
|
+
resolve33(true);
|
|
28277
28425
|
return;
|
|
28278
28426
|
}
|
|
28279
|
-
|
|
28427
|
+
resolve33(/^y(es)?$/i.test(trimmed));
|
|
28280
28428
|
});
|
|
28281
28429
|
});
|
|
28282
28430
|
}
|
|
@@ -28355,26 +28503,26 @@ function buildStageResult(input) {
|
|
|
28355
28503
|
}
|
|
28356
28504
|
|
|
28357
28505
|
// src/core/local-executor.ts
|
|
28358
|
-
var
|
|
28359
|
-
var
|
|
28506
|
+
var fs24 = __toESM(require("fs"));
|
|
28507
|
+
var path23 = __toESM(require("path"));
|
|
28360
28508
|
|
|
28361
28509
|
// src/core/slot-registry.ts
|
|
28362
|
-
var
|
|
28363
|
-
var
|
|
28510
|
+
var fs23 = __toESM(require("fs"));
|
|
28511
|
+
var path22 = __toESM(require("path"));
|
|
28364
28512
|
function getSlotRegistryDir(repoPath) {
|
|
28365
|
-
return
|
|
28513
|
+
return path22.join(getHarnessDir(repoPath), "slots");
|
|
28366
28514
|
}
|
|
28367
28515
|
function getSlotRegistryPath(repoPath, agentId) {
|
|
28368
|
-
return
|
|
28516
|
+
return path22.join(getSlotRegistryDir(repoPath), `agent-${agentId}.json`);
|
|
28369
28517
|
}
|
|
28370
28518
|
function isSlotResumable(session) {
|
|
28371
28519
|
return session?.status === "failed" || session?.status === "starting";
|
|
28372
28520
|
}
|
|
28373
28521
|
function readSlotRegistry(repoPath, agentId) {
|
|
28374
28522
|
const file = getSlotRegistryPath(repoPath, agentId);
|
|
28375
|
-
if (!
|
|
28523
|
+
if (!fs23.existsSync(file)) return void 0;
|
|
28376
28524
|
try {
|
|
28377
|
-
const raw = JSON.parse(
|
|
28525
|
+
const raw = JSON.parse(fs23.readFileSync(file, "utf8"));
|
|
28378
28526
|
const result = SlotRegistryEntrySchema.safeParse(raw);
|
|
28379
28527
|
return result.success ? result.data : void 0;
|
|
28380
28528
|
} catch {
|
|
@@ -28384,7 +28532,7 @@ function readSlotRegistry(repoPath, agentId) {
|
|
|
28384
28532
|
|
|
28385
28533
|
// src/core/local-executor.ts
|
|
28386
28534
|
function resolveRepoPath(repoPath) {
|
|
28387
|
-
return
|
|
28535
|
+
return path23.resolve(repoPath);
|
|
28388
28536
|
}
|
|
28389
28537
|
function buildPreviewUrlsFromPorts(ports, env3) {
|
|
28390
28538
|
const urls = {};
|
|
@@ -28436,14 +28584,14 @@ function substituteAgentId(value, agentId) {
|
|
|
28436
28584
|
function resolveStageScriptPath(repoPath, stage) {
|
|
28437
28585
|
const harnessDir = getHarnessDir(repoPath);
|
|
28438
28586
|
if (stage.script) {
|
|
28439
|
-
return
|
|
28587
|
+
return path23.join(harnessDir, stage.script);
|
|
28440
28588
|
}
|
|
28441
28589
|
if (stage.command) {
|
|
28442
28590
|
const scriptName = stage.command.split(/\s+/)[0].replace(/^\.\/\.har\//, "").replace(/^\.\//, "");
|
|
28443
|
-
return
|
|
28591
|
+
return path23.join(harnessDir, scriptName);
|
|
28444
28592
|
}
|
|
28445
|
-
const stageScript =
|
|
28446
|
-
if (
|
|
28593
|
+
const stageScript = path23.join(harnessDir, "stages", `${stage.id}.sh`);
|
|
28594
|
+
if (fs24.existsSync(stageScript)) {
|
|
28447
28595
|
return stageScript;
|
|
28448
28596
|
}
|
|
28449
28597
|
throw new Error(
|
|
@@ -28458,7 +28606,7 @@ function buildExecutionPlan(repoPath, stage, options) {
|
|
|
28458
28606
|
...harnessEnv,
|
|
28459
28607
|
...stage.env ?? {}
|
|
28460
28608
|
};
|
|
28461
|
-
const cwd = stage.cwd ?
|
|
28609
|
+
const cwd = stage.cwd ? path23.resolve(resolvedRepo, stage.cwd) : resolvedRepo;
|
|
28462
28610
|
const extraArgs = options.args ?? [];
|
|
28463
28611
|
const launchFlagArgs = [];
|
|
28464
28612
|
if (stage.kind === "launch" && options.launchFlags) {
|
|
@@ -28535,19 +28683,19 @@ var LocalScriptExecutor = class {
|
|
|
28535
28683
|
const repoPath = resolveRepoPath(ctx.repoPath);
|
|
28536
28684
|
const harnessDir = getHarnessDir(repoPath);
|
|
28537
28685
|
const artifactsDirName = getArtifactsDir(repoPath);
|
|
28538
|
-
const artifactsDir =
|
|
28539
|
-
if (!
|
|
28686
|
+
const artifactsDir = path23.join(harnessDir, artifactsDirName);
|
|
28687
|
+
if (!fs24.existsSync(artifactsDir)) return [];
|
|
28540
28688
|
const entries = [];
|
|
28541
28689
|
const walk = (dir, prefix) => {
|
|
28542
|
-
for (const entry of
|
|
28543
|
-
const full =
|
|
28544
|
-
const relative2 =
|
|
28690
|
+
for (const entry of fs24.readdirSync(dir, { withFileTypes: true })) {
|
|
28691
|
+
const full = path23.join(dir, entry.name);
|
|
28692
|
+
const relative2 = path23.join(prefix, entry.name);
|
|
28545
28693
|
if (entry.isDirectory()) {
|
|
28546
28694
|
walk(full, relative2);
|
|
28547
28695
|
continue;
|
|
28548
28696
|
}
|
|
28549
28697
|
if (filter?.stageId && !relative2.includes(filter.stageId)) continue;
|
|
28550
|
-
const stat =
|
|
28698
|
+
const stat = fs24.statSync(full);
|
|
28551
28699
|
entries.push({
|
|
28552
28700
|
path: full,
|
|
28553
28701
|
relativePath: relative2,
|
|
@@ -28563,7 +28711,7 @@ var LocalScriptExecutor = class {
|
|
|
28563
28711
|
var localScriptExecutor = new LocalScriptExecutor();
|
|
28564
28712
|
|
|
28565
28713
|
// src/core/control-sync.ts
|
|
28566
|
-
var
|
|
28714
|
+
var path37 = __toESM(require("path"));
|
|
28567
28715
|
|
|
28568
28716
|
// src/core/control-config.ts
|
|
28569
28717
|
var DEFAULT_CONTROL_API_URL = "http://localhost:3847";
|
|
@@ -28575,32 +28723,32 @@ function isControlEnabled() {
|
|
|
28575
28723
|
}
|
|
28576
28724
|
|
|
28577
28725
|
// src/core/control-registry.ts
|
|
28578
|
-
var
|
|
28726
|
+
var fs25 = __toESM(require("fs"));
|
|
28579
28727
|
var os4 = __toESM(require("os"));
|
|
28580
|
-
var
|
|
28728
|
+
var path24 = __toESM(require("path"));
|
|
28581
28729
|
function getRegistryPath() {
|
|
28582
28730
|
if (process.env.HAR_CONTROL_REGISTRY_PATH) {
|
|
28583
|
-
return
|
|
28731
|
+
return path24.resolve(process.env.HAR_CONTROL_REGISTRY_PATH);
|
|
28584
28732
|
}
|
|
28585
|
-
return
|
|
28733
|
+
return path24.join(os4.homedir(), ".har", "repos.json");
|
|
28586
28734
|
}
|
|
28587
28735
|
function readRegistry() {
|
|
28588
28736
|
const registryPath = getRegistryPath();
|
|
28589
28737
|
try {
|
|
28590
|
-
if (!
|
|
28591
|
-
return JSON.parse(
|
|
28738
|
+
if (!fs25.existsSync(registryPath)) return { repos: [] };
|
|
28739
|
+
return JSON.parse(fs25.readFileSync(registryPath, "utf8"));
|
|
28592
28740
|
} catch {
|
|
28593
28741
|
return { repos: [] };
|
|
28594
28742
|
}
|
|
28595
28743
|
}
|
|
28596
28744
|
function writeRegistry(registry2) {
|
|
28597
28745
|
const registryPath = getRegistryPath();
|
|
28598
|
-
|
|
28599
|
-
|
|
28746
|
+
fs25.mkdirSync(path24.dirname(registryPath), { recursive: true });
|
|
28747
|
+
fs25.writeFileSync(registryPath, JSON.stringify(registry2, null, 2) + "\n");
|
|
28600
28748
|
}
|
|
28601
28749
|
function recordRepoForControlSync(repoPath) {
|
|
28602
28750
|
if (process.env.HAR_CONTROL_DISABLED === "true") return;
|
|
28603
|
-
const resolved =
|
|
28751
|
+
const resolved = path24.resolve(repoPath);
|
|
28604
28752
|
if (!readManifest(resolved)) return;
|
|
28605
28753
|
const registry2 = readRegistry();
|
|
28606
28754
|
if (registry2.repos.includes(resolved)) return;
|
|
@@ -28609,7 +28757,7 @@ function recordRepoForControlSync(repoPath) {
|
|
|
28609
28757
|
}
|
|
28610
28758
|
function listRegisteredRepos() {
|
|
28611
28759
|
const registry2 = readRegistry();
|
|
28612
|
-
const kept = registry2.repos.filter((repoPath) =>
|
|
28760
|
+
const kept = registry2.repos.filter((repoPath) => fs25.existsSync(repoPath) && readManifest(repoPath));
|
|
28613
28761
|
if (kept.length !== registry2.repos.length) {
|
|
28614
28762
|
writeRegistry({ repos: kept });
|
|
28615
28763
|
}
|
|
@@ -28617,17 +28765,17 @@ function listRegisteredRepos() {
|
|
|
28617
28765
|
}
|
|
28618
28766
|
|
|
28619
28767
|
// src/core/slot-status.ts
|
|
28620
|
-
var
|
|
28768
|
+
var fs30 = __toESM(require("fs"));
|
|
28621
28769
|
var os7 = __toESM(require("os"));
|
|
28622
|
-
var
|
|
28770
|
+
var path30 = __toESM(require("path"));
|
|
28623
28771
|
var import_child_process7 = require("child_process");
|
|
28624
28772
|
|
|
28625
28773
|
// src/core/runs.ts
|
|
28626
28774
|
var crypto2 = __toESM(require("crypto"));
|
|
28627
28775
|
var import_child_process2 = require("child_process");
|
|
28628
|
-
var
|
|
28776
|
+
var fs26 = __toESM(require("fs"));
|
|
28629
28777
|
var os5 = __toESM(require("os"));
|
|
28630
|
-
var
|
|
28778
|
+
var path25 = __toESM(require("path"));
|
|
28631
28779
|
var RUNS_DIR = "runs";
|
|
28632
28780
|
function gitCommonDir(cwd) {
|
|
28633
28781
|
try {
|
|
@@ -28636,7 +28784,7 @@ function gitCommonDir(cwd) {
|
|
|
28636
28784
|
encoding: "utf8",
|
|
28637
28785
|
stdio: ["pipe", "pipe", "ignore"]
|
|
28638
28786
|
}).trim();
|
|
28639
|
-
return out ?
|
|
28787
|
+
return out ? path25.resolve(cwd, out) : void 0;
|
|
28640
28788
|
} catch {
|
|
28641
28789
|
return void 0;
|
|
28642
28790
|
}
|
|
@@ -28658,7 +28806,7 @@ function gitPrefix(cwd) {
|
|
|
28658
28806
|
}
|
|
28659
28807
|
}
|
|
28660
28808
|
function getRunsDir(harnessRoot) {
|
|
28661
|
-
return
|
|
28809
|
+
return path25.join(getHarnessDir(harnessRoot), RUNS_DIR);
|
|
28662
28810
|
}
|
|
28663
28811
|
function formatLocalDate(d2) {
|
|
28664
28812
|
const y2 = d2.getFullYear();
|
|
@@ -28678,24 +28826,24 @@ function buildRunRelativePath(stageId, agentId, startedAt, runId, runsDir) {
|
|
|
28678
28826
|
const timePart = formatLocalTime(started);
|
|
28679
28827
|
const agentPart = agentId !== void 0 ? `_agent-${agentId}` : "";
|
|
28680
28828
|
let filename = `${timePart}_${stageId}${agentPart}.json`;
|
|
28681
|
-
const fullPath =
|
|
28682
|
-
if (
|
|
28829
|
+
const fullPath = path25.join(runsDir, dateFolder, filename);
|
|
28830
|
+
if (fs26.existsSync(fullPath)) {
|
|
28683
28831
|
filename = `${timePart}_${stageId}${agentPart}-${runId.slice(0, 8)}.json`;
|
|
28684
28832
|
}
|
|
28685
|
-
return
|
|
28833
|
+
return path25.join(dateFolder, filename);
|
|
28686
28834
|
}
|
|
28687
28835
|
function resolveRunFilePath(harnessRoot, run2) {
|
|
28688
28836
|
if (run2.relativePath) {
|
|
28689
|
-
return
|
|
28837
|
+
return path25.join(getRunsDir(harnessRoot), run2.relativePath);
|
|
28690
28838
|
}
|
|
28691
|
-
return
|
|
28839
|
+
return path25.join(getRunsDir(harnessRoot), `${run2.runId}.json`);
|
|
28692
28840
|
}
|
|
28693
28841
|
function collectRunFiles(runsDir) {
|
|
28694
|
-
if (!
|
|
28842
|
+
if (!fs26.existsSync(runsDir)) return [];
|
|
28695
28843
|
const files = [];
|
|
28696
28844
|
const walk = (dir) => {
|
|
28697
|
-
for (const entry of
|
|
28698
|
-
const full =
|
|
28845
|
+
for (const entry of fs26.readdirSync(dir, { withFileTypes: true })) {
|
|
28846
|
+
const full = path25.join(dir, entry.name);
|
|
28699
28847
|
if (entry.isDirectory()) {
|
|
28700
28848
|
walk(full);
|
|
28701
28849
|
} else if (entry.name.endsWith(".json")) {
|
|
@@ -28709,30 +28857,30 @@ function collectRunFiles(runsDir) {
|
|
|
28709
28857
|
function resolveAgentWorkDir(harnessRoot, agentId) {
|
|
28710
28858
|
if (agentId === void 0) return void 0;
|
|
28711
28859
|
const entry = readSlotRegistry(harnessRoot, agentId);
|
|
28712
|
-
if (entry?.workDir &&
|
|
28860
|
+
if (entry?.workDir && fs26.existsSync(entry.workDir)) return entry.workDir;
|
|
28713
28861
|
const env3 = readHarnessEnv(harnessRoot);
|
|
28714
|
-
const projectName = env3.HARNESS_PROJECT_NAME ??
|
|
28715
|
-
const worktreeDir =
|
|
28862
|
+
const projectName = env3.HARNESS_PROJECT_NAME ?? path25.basename(harnessRoot);
|
|
28863
|
+
const worktreeDir = path25.join(os5.homedir(), "worktrees", `${projectName}-agent-${agentId}`);
|
|
28716
28864
|
const relPrefix = gitPrefix(harnessRoot);
|
|
28717
28865
|
const sessionEnvFiles = [];
|
|
28718
|
-
const worktreesRoot =
|
|
28719
|
-
if (
|
|
28866
|
+
const worktreesRoot = path25.join(os5.homedir(), "worktrees");
|
|
28867
|
+
if (fs26.existsSync(worktreesRoot)) {
|
|
28720
28868
|
const suffix = `-har-agent-${agentId}-`;
|
|
28721
|
-
for (const entry2 of
|
|
28869
|
+
for (const entry2 of fs26.readdirSync(worktreesRoot, { withFileTypes: true })) {
|
|
28722
28870
|
if (!entry2.isDirectory() || !entry2.name.includes(suffix)) continue;
|
|
28723
|
-
const sessionDir =
|
|
28871
|
+
const sessionDir = path25.join(worktreesRoot, entry2.name);
|
|
28724
28872
|
if (!sameGitCheckout(harnessRoot, sessionDir)) continue;
|
|
28725
|
-
sessionEnvFiles.push(
|
|
28873
|
+
sessionEnvFiles.push(path25.join(sessionDir, relPrefix, `.env.agent.${agentId}`));
|
|
28726
28874
|
}
|
|
28727
28875
|
}
|
|
28728
28876
|
const candidates = [
|
|
28729
|
-
|
|
28730
|
-
|
|
28877
|
+
path25.join(worktreeDir, `.env.agent.${agentId}`),
|
|
28878
|
+
path25.join(harnessRoot, `.env.agent.${agentId}`),
|
|
28731
28879
|
...sessionEnvFiles.sort()
|
|
28732
28880
|
];
|
|
28733
28881
|
for (const envFile of candidates) {
|
|
28734
|
-
if (!
|
|
28735
|
-
const content =
|
|
28882
|
+
if (!fs26.existsSync(envFile)) continue;
|
|
28883
|
+
const content = fs26.readFileSync(envFile, "utf8");
|
|
28736
28884
|
const match = content.match(/^REPO_ROOT=(.+)$/m);
|
|
28737
28885
|
if (match) return match[1].trim();
|
|
28738
28886
|
}
|
|
@@ -28744,11 +28892,11 @@ function createRun(ctx, meta) {
|
|
|
28744
28892
|
const startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
28745
28893
|
const runId = crypto2.randomUUID();
|
|
28746
28894
|
const relativePath = buildRunRelativePath(meta.stageId, meta.agentId, startedAt, runId, runsDir);
|
|
28747
|
-
const runFilePath =
|
|
28748
|
-
|
|
28895
|
+
const runFilePath = path25.join(runsDir, relativePath);
|
|
28896
|
+
fs26.mkdirSync(path25.dirname(runFilePath), { recursive: true });
|
|
28749
28897
|
const run2 = RunRecordSchema.parse({
|
|
28750
28898
|
runId,
|
|
28751
|
-
repoPath:
|
|
28899
|
+
repoPath: path25.resolve(ctx.repoPath),
|
|
28752
28900
|
harnessRoot,
|
|
28753
28901
|
stageId: meta.stageId,
|
|
28754
28902
|
kind: meta.kind,
|
|
@@ -28759,7 +28907,7 @@ function createRun(ctx, meta) {
|
|
|
28759
28907
|
relativePath,
|
|
28760
28908
|
trigger: ctx.trigger ?? "cli"
|
|
28761
28909
|
});
|
|
28762
|
-
|
|
28910
|
+
fs26.writeFileSync(runFilePath, JSON.stringify(run2, null, 2) + "\n");
|
|
28763
28911
|
return run2;
|
|
28764
28912
|
}
|
|
28765
28913
|
function finishRun(repoPath, runId, update) {
|
|
@@ -28778,13 +28926,13 @@ function finishRun(repoPath, runId, update) {
|
|
|
28778
28926
|
finishedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
28779
28927
|
});
|
|
28780
28928
|
const runPath = resolveRunFilePath(harnessRoot, finished);
|
|
28781
|
-
|
|
28929
|
+
fs26.writeFileSync(runPath, JSON.stringify(finished, null, 2) + "\n");
|
|
28782
28930
|
return finished;
|
|
28783
28931
|
}
|
|
28784
28932
|
function findRunRecord(harnessRoot, runId) {
|
|
28785
28933
|
const runsDir = getRunsDir(harnessRoot);
|
|
28786
28934
|
for (const filePath of collectRunFiles(runsDir)) {
|
|
28787
|
-
const parsed = RunRecordSchema.safeParse(JSON.parse(
|
|
28935
|
+
const parsed = RunRecordSchema.safeParse(JSON.parse(fs26.readFileSync(filePath, "utf8")));
|
|
28788
28936
|
if (parsed.success && parsed.data.runId === runId) {
|
|
28789
28937
|
return parsed.data;
|
|
28790
28938
|
}
|
|
@@ -28796,10 +28944,10 @@ function getRun(repoPath, runId) {
|
|
|
28796
28944
|
}
|
|
28797
28945
|
function listRuns(repoPath, filter = {}) {
|
|
28798
28946
|
const runsDir = getRunsDir(resolveHarnessRoot(repoPath));
|
|
28799
|
-
if (!
|
|
28947
|
+
if (!fs26.existsSync(runsDir)) return [];
|
|
28800
28948
|
const runs = [];
|
|
28801
28949
|
for (const filePath of collectRunFiles(runsDir)) {
|
|
28802
|
-
const parsed = RunRecordSchema.safeParse(JSON.parse(
|
|
28950
|
+
const parsed = RunRecordSchema.safeParse(JSON.parse(fs26.readFileSync(filePath, "utf8")));
|
|
28803
28951
|
if (!parsed.success) continue;
|
|
28804
28952
|
if (filter.stageId && parsed.data.stageId !== filter.stageId) continue;
|
|
28805
28953
|
runs.push(parsed.data);
|
|
@@ -28812,17 +28960,17 @@ function listRuns(repoPath, filter = {}) {
|
|
|
28812
28960
|
}
|
|
28813
28961
|
|
|
28814
28962
|
// src/core/slot-preflight.ts
|
|
28815
|
-
var
|
|
28963
|
+
var path29 = __toESM(require("path"));
|
|
28816
28964
|
|
|
28817
28965
|
// src/core/control-port.ts
|
|
28818
28966
|
var import_child_process4 = require("child_process");
|
|
28819
|
-
var
|
|
28820
|
-
var
|
|
28967
|
+
var fs28 = __toESM(require("fs"));
|
|
28968
|
+
var path27 = __toESM(require("path"));
|
|
28821
28969
|
|
|
28822
28970
|
// src/core/slot-ports.ts
|
|
28823
28971
|
var import_child_process3 = require("child_process");
|
|
28824
|
-
var
|
|
28825
|
-
var
|
|
28972
|
+
var fs27 = __toESM(require("fs"));
|
|
28973
|
+
var path26 = __toESM(require("path"));
|
|
28826
28974
|
function portStep(env3) {
|
|
28827
28975
|
return Number(env3.HARNESS_PORT_STEP ?? 10);
|
|
28828
28976
|
}
|
|
@@ -28878,7 +29026,7 @@ function allocateAppPorts(repoPath, agentId) {
|
|
|
28878
29026
|
}
|
|
28879
29027
|
function harnessUsesPm2(repoPath) {
|
|
28880
29028
|
const harnessRoot = resolveHarnessRoot(repoPath);
|
|
28881
|
-
return
|
|
29029
|
+
return fs27.existsSync(path26.join(harnessRoot, ".har", "ecosystem.agent.template.cjs"));
|
|
28882
29030
|
}
|
|
28883
29031
|
|
|
28884
29032
|
// src/core/control-port.ts
|
|
@@ -28933,13 +29081,13 @@ function controlDefaultPortWarnings(containers, defaultFrontendPort, allocatedFr
|
|
|
28933
29081
|
return [formatControlDefaultPortWarning(control.name, defaultFrontendPort, allocatedFrontend)];
|
|
28934
29082
|
}
|
|
28935
29083
|
function inspectControlUpReadiness(repoPath) {
|
|
28936
|
-
const resolved =
|
|
29084
|
+
const resolved = path27.resolve(repoPath);
|
|
28937
29085
|
const port = parseControlHostPort();
|
|
28938
29086
|
const containers = listDockerContainers();
|
|
28939
29087
|
const control = controlContainerOnPort(containers, port);
|
|
28940
29088
|
const warnings = [];
|
|
28941
|
-
const controlHarnessDir =
|
|
28942
|
-
const harnessSlot1Active =
|
|
29089
|
+
const controlHarnessDir = path27.join(resolved, "control", ".har");
|
|
29090
|
+
const harnessSlot1Active = fs28.existsSync(controlHarnessDir) && readSlotRegistry(path27.join(resolved, "control"), 1)?.status === "active";
|
|
28943
29091
|
if (control) {
|
|
28944
29092
|
return {
|
|
28945
29093
|
warnings,
|
|
@@ -28970,9 +29118,9 @@ function inspectControlUpReadiness(repoPath) {
|
|
|
28970
29118
|
}
|
|
28971
29119
|
|
|
28972
29120
|
// src/core/slot-launch-guard-occupied.ts
|
|
28973
|
-
var
|
|
29121
|
+
var fs29 = __toESM(require("fs"));
|
|
28974
29122
|
var os6 = __toESM(require("os"));
|
|
28975
|
-
var
|
|
29123
|
+
var path28 = __toESM(require("path"));
|
|
28976
29124
|
var import_child_process5 = require("child_process");
|
|
28977
29125
|
function runGit(cwd, args) {
|
|
28978
29126
|
try {
|
|
@@ -28991,7 +29139,7 @@ function worktreeDirty(worktreePath) {
|
|
|
28991
29139
|
}
|
|
28992
29140
|
function gitCommonDir2(cwd) {
|
|
28993
29141
|
const out = runGit(cwd, "rev-parse --git-common-dir");
|
|
28994
|
-
return out ?
|
|
29142
|
+
return out ? path28.resolve(cwd, out) : void 0;
|
|
28995
29143
|
}
|
|
28996
29144
|
function sameGitCheckout2(a2, b2) {
|
|
28997
29145
|
const left2 = gitCommonDir2(a2);
|
|
@@ -29000,20 +29148,20 @@ function sameGitCheckout2(a2, b2) {
|
|
|
29000
29148
|
}
|
|
29001
29149
|
function discoverWorktree(harnessRoot, agentId, projectName) {
|
|
29002
29150
|
const session = readSlotRegistry(harnessRoot, agentId);
|
|
29003
|
-
if (session?.worktreePath &&
|
|
29151
|
+
if (session?.worktreePath && fs29.existsSync(session.worktreePath)) {
|
|
29004
29152
|
return session.worktreePath;
|
|
29005
29153
|
}
|
|
29006
|
-
const legacy =
|
|
29007
|
-
if (
|
|
29008
|
-
const worktreesRoot =
|
|
29009
|
-
if (!
|
|
29154
|
+
const legacy = path28.join(os6.homedir(), "worktrees", `${projectName}-agent-${agentId}`);
|
|
29155
|
+
if (fs29.existsSync(legacy) && sameGitCheckout2(harnessRoot, legacy)) return legacy;
|
|
29156
|
+
const worktreesRoot = path28.join(os6.homedir(), "worktrees");
|
|
29157
|
+
if (!fs29.existsSync(worktreesRoot)) return void 0;
|
|
29010
29158
|
const suffix = `-har-agent-${agentId}-`;
|
|
29011
29159
|
const relPrefix = runGit(harnessRoot, "rev-parse --show-prefix") ?? "";
|
|
29012
|
-
for (const name of
|
|
29160
|
+
for (const name of fs29.readdirSync(worktreesRoot)) {
|
|
29013
29161
|
if (!name.includes(suffix)) continue;
|
|
29014
|
-
const candidate =
|
|
29162
|
+
const candidate = path28.join(worktreesRoot, name);
|
|
29015
29163
|
if (!sameGitCheckout2(harnessRoot, candidate)) continue;
|
|
29016
|
-
if (!
|
|
29164
|
+
if (!fs29.existsSync(path28.join(candidate, relPrefix, `.env.agent.${agentId}`))) continue;
|
|
29017
29165
|
return candidate;
|
|
29018
29166
|
}
|
|
29019
29167
|
return void 0;
|
|
@@ -29021,13 +29169,13 @@ function discoverWorktree(harnessRoot, agentId, projectName) {
|
|
|
29021
29169
|
function collectOccupiedSlot(repoPath, agentId) {
|
|
29022
29170
|
const harnessRoot = resolveHarnessRoot(repoPath);
|
|
29023
29171
|
const env3 = readHarnessEnv(harnessRoot);
|
|
29024
|
-
const projectName = env3.HARNESS_PROJECT_NAME ??
|
|
29172
|
+
const projectName = env3.HARNESS_PROJECT_NAME ?? path28.basename(harnessRoot);
|
|
29025
29173
|
const session = readSlotRegistry(harnessRoot, agentId);
|
|
29026
29174
|
const worktreePath = discoverWorktree(harnessRoot, agentId, projectName);
|
|
29027
29175
|
const workDir = session?.workDir;
|
|
29028
|
-
const envInWorkDir = workDir ?
|
|
29029
|
-
const envInRoot =
|
|
29030
|
-
const envInWorktree = worktreePath ?
|
|
29176
|
+
const envInWorkDir = workDir ? fs29.existsSync(path28.join(workDir, `.env.agent.${agentId}`)) : false;
|
|
29177
|
+
const envInRoot = fs29.existsSync(path28.join(harnessRoot, `.env.agent.${agentId}`));
|
|
29178
|
+
const envInWorktree = worktreePath ? fs29.existsSync(path28.join(worktreePath, `.env.agent.${agentId}`)) : false;
|
|
29031
29179
|
const active = session !== void 0 && session.status !== "completed" || envInWorkDir || envInRoot || envInWorktree;
|
|
29032
29180
|
if (!active) return void 0;
|
|
29033
29181
|
const dirty = worktreePath ? worktreeDirty(worktreePath) : void 0;
|
|
@@ -29180,7 +29328,7 @@ function checkInfraPort(env3, varName, defaultPort, scanStart, scanEnd) {
|
|
|
29180
29328
|
function inspectSlotReadiness(repoPath, agentId, options = {}) {
|
|
29181
29329
|
const harnessRoot = resolveHarnessRoot(repoPath);
|
|
29182
29330
|
const env3 = readHarnessEnv(harnessRoot);
|
|
29183
|
-
const projectName = env3.HARNESS_PROJECT_NAME ??
|
|
29331
|
+
const projectName = env3.HARNESS_PROJECT_NAME ?? path29.basename(harnessRoot);
|
|
29184
29332
|
const usesPm2 = harnessUsesPm2(repoPath);
|
|
29185
29333
|
const blockers = [];
|
|
29186
29334
|
const remediations = [];
|
|
@@ -29382,7 +29530,7 @@ function readWorktreeBranch(worktreePath) {
|
|
|
29382
29530
|
}
|
|
29383
29531
|
function gitCommonDir3(cwd) {
|
|
29384
29532
|
const out = runGit2(cwd, "rev-parse --git-common-dir");
|
|
29385
|
-
return out ?
|
|
29533
|
+
return out ? path30.resolve(cwd, out) : void 0;
|
|
29386
29534
|
}
|
|
29387
29535
|
function sameGitCheckout3(a2, b2) {
|
|
29388
29536
|
const left2 = gitCommonDir3(a2);
|
|
@@ -29394,12 +29542,12 @@ function gitPrefix2(cwd) {
|
|
|
29394
29542
|
return out ?? "";
|
|
29395
29543
|
}
|
|
29396
29544
|
function discoverSessionWorktreePath(harnessRoot, agentId) {
|
|
29397
|
-
const worktreesRoot =
|
|
29398
|
-
if (!
|
|
29545
|
+
const worktreesRoot = path30.join(os7.homedir(), "worktrees");
|
|
29546
|
+
if (!fs30.existsSync(worktreesRoot)) return void 0;
|
|
29399
29547
|
const suffix = `-har-agent-${agentId}-`;
|
|
29400
29548
|
const relPrefix = gitPrefix2(harnessRoot);
|
|
29401
|
-
const matches =
|
|
29402
|
-
(candidate) => sameGitCheckout3(harnessRoot, candidate) &&
|
|
29549
|
+
const matches = fs30.readdirSync(worktreesRoot, { withFileTypes: true }).filter((entry) => entry.isDirectory() && entry.name.includes(suffix)).map((entry) => path30.join(worktreesRoot, entry.name)).filter(
|
|
29550
|
+
(candidate) => sameGitCheckout3(harnessRoot, candidate) && fs30.existsSync(path30.join(candidate, relPrefix, `.env.agent.${agentId}`))
|
|
29403
29551
|
);
|
|
29404
29552
|
return matches.sort()[0];
|
|
29405
29553
|
}
|
|
@@ -29474,18 +29622,18 @@ function detectPm2Issue(projectName, agentId, session, procs) {
|
|
|
29474
29622
|
}
|
|
29475
29623
|
function collectSlotStatus(harnessRoot, agentId, runs, pm2Procs) {
|
|
29476
29624
|
const env3 = readHarnessEnv(harnessRoot);
|
|
29477
|
-
const projectName = env3.HARNESS_PROJECT_NAME ??
|
|
29625
|
+
const projectName = env3.HARNESS_PROJECT_NAME ?? path30.basename(harnessRoot);
|
|
29478
29626
|
const session = readSlotRegistry(harnessRoot, agentId);
|
|
29479
|
-
const legacyWorktreePath =
|
|
29627
|
+
const legacyWorktreePath = path30.join(
|
|
29480
29628
|
os7.homedir(),
|
|
29481
29629
|
"worktrees",
|
|
29482
29630
|
`${projectName}-agent-${agentId}`
|
|
29483
29631
|
);
|
|
29484
|
-
const worktreePath = session?.worktreePath &&
|
|
29632
|
+
const worktreePath = session?.worktreePath && fs30.existsSync(session.worktreePath) ? session.worktreePath : fs30.existsSync(legacyWorktreePath) ? legacyWorktreePath : discoverSessionWorktreePath(harnessRoot, agentId);
|
|
29485
29633
|
const workDir = resolveAgentWorkDir(harnessRoot, agentId);
|
|
29486
|
-
const envInWorkDir = workDir ?
|
|
29487
|
-
const envInRoot =
|
|
29488
|
-
const envInWorktree = worktreePath ?
|
|
29634
|
+
const envInWorkDir = workDir ? fs30.existsSync(path30.join(workDir, `.env.agent.${agentId}`)) : false;
|
|
29635
|
+
const envInRoot = fs30.existsSync(path30.join(harnessRoot, `.env.agent.${agentId}`));
|
|
29636
|
+
const envInWorktree = worktreePath ? fs30.existsSync(path30.join(worktreePath, `.env.agent.${agentId}`)) : false;
|
|
29489
29637
|
const active = session !== void 0 && session.status !== "completed" || envInWorkDir || envInRoot || envInWorktree;
|
|
29490
29638
|
const latest = latestRunForAgent(runs, agentId);
|
|
29491
29639
|
const verifyRun = runs.find((r2) => r2.agentId === agentId && r2.stageId === "verify");
|
|
@@ -29538,7 +29686,7 @@ function collectEnvironmentStatus(repoPath) {
|
|
|
29538
29686
|
const slotIds = getAgentSlotIds(harnessRoot);
|
|
29539
29687
|
const pm2Procs = listPm2Processes2();
|
|
29540
29688
|
return {
|
|
29541
|
-
repoPath:
|
|
29689
|
+
repoPath: path30.resolve(repoPath),
|
|
29542
29690
|
harnessRoot,
|
|
29543
29691
|
gitRemote: readGitRemote(harnessRoot),
|
|
29544
29692
|
profile: manifest?.profile,
|
|
@@ -29549,13 +29697,13 @@ function collectEnvironmentStatus(repoPath) {
|
|
|
29549
29697
|
|
|
29550
29698
|
// src/core/validations.ts
|
|
29551
29699
|
var crypto3 = __toESM(require("crypto"));
|
|
29552
|
-
var
|
|
29553
|
-
var
|
|
29700
|
+
var fs32 = __toESM(require("fs"));
|
|
29701
|
+
var path32 = __toESM(require("path"));
|
|
29554
29702
|
|
|
29555
29703
|
// src/core/change-batch.ts
|
|
29556
|
-
var
|
|
29704
|
+
var fs31 = __toESM(require("fs"));
|
|
29557
29705
|
var os8 = __toESM(require("os"));
|
|
29558
|
-
var
|
|
29706
|
+
var path31 = __toESM(require("path"));
|
|
29559
29707
|
function git(checkoutDir, args, env3) {
|
|
29560
29708
|
const result = run(`git ${args}`, { cwd: checkoutDir, env: env3 });
|
|
29561
29709
|
if (result.code !== 0) {
|
|
@@ -29571,7 +29719,7 @@ function isCheckoutRoot(checkoutDir) {
|
|
|
29571
29719
|
const toplevel = tryGit(checkoutDir, "rev-parse --show-toplevel");
|
|
29572
29720
|
if (!toplevel) return false;
|
|
29573
29721
|
try {
|
|
29574
|
-
return
|
|
29722
|
+
return fs31.realpathSync(toplevel) === fs31.realpathSync(checkoutDir);
|
|
29575
29723
|
} catch {
|
|
29576
29724
|
return false;
|
|
29577
29725
|
}
|
|
@@ -29593,7 +29741,7 @@ function isMergeOrRebaseInProgress(checkoutDir) {
|
|
|
29593
29741
|
if (tryGit(checkoutDir, "rev-parse -q --verify MERGE_HEAD") !== void 0) return true;
|
|
29594
29742
|
for (const dir of ["rebase-merge", "rebase-apply"]) {
|
|
29595
29743
|
const gitPath = tryGit(checkoutDir, `rev-parse --git-path ${dir}`);
|
|
29596
|
-
if (gitPath &&
|
|
29744
|
+
if (gitPath && fs31.existsSync(path31.resolve(checkoutDir, gitPath))) return true;
|
|
29597
29745
|
}
|
|
29598
29746
|
return false;
|
|
29599
29747
|
}
|
|
@@ -29616,8 +29764,8 @@ function parseNameStatus(output) {
|
|
|
29616
29764
|
return files;
|
|
29617
29765
|
}
|
|
29618
29766
|
function computeWorktreeSnapshot(checkoutDir) {
|
|
29619
|
-
const tmpDir =
|
|
29620
|
-
const tmpIndex =
|
|
29767
|
+
const tmpDir = fs31.mkdtempSync(path31.join(os8.tmpdir(), "har-idx-"));
|
|
29768
|
+
const tmpIndex = path31.join(tmpDir, "index");
|
|
29621
29769
|
const env3 = { GIT_INDEX_FILE: tmpIndex };
|
|
29622
29770
|
try {
|
|
29623
29771
|
const headTree = getHeadTree(checkoutDir);
|
|
@@ -29640,35 +29788,35 @@ function computeWorktreeSnapshot(checkoutDir) {
|
|
|
29640
29788
|
changedFiles: parseNameStatus(diffOutput)
|
|
29641
29789
|
};
|
|
29642
29790
|
} finally {
|
|
29643
|
-
|
|
29791
|
+
fs31.rmSync(tmpDir, { recursive: true, force: true });
|
|
29644
29792
|
}
|
|
29645
29793
|
}
|
|
29646
29794
|
|
|
29647
29795
|
// src/core/validations.ts
|
|
29648
29796
|
var VALIDATIONS_DIR = "validations";
|
|
29649
29797
|
function getValidationsDir(checkoutDir) {
|
|
29650
|
-
return
|
|
29798
|
+
return path32.join(checkoutDir, ".har", VALIDATIONS_DIR);
|
|
29651
29799
|
}
|
|
29652
29800
|
function validationPath(checkoutDir, treeHash) {
|
|
29653
|
-
return
|
|
29801
|
+
return path32.join(getValidationsDir(checkoutDir), `${treeHash}.json`);
|
|
29654
29802
|
}
|
|
29655
29803
|
function writeRecord(checkoutDir, record2) {
|
|
29656
29804
|
const dir = getValidationsDir(checkoutDir);
|
|
29657
|
-
|
|
29658
|
-
|
|
29805
|
+
fs32.mkdirSync(dir, { recursive: true });
|
|
29806
|
+
fs32.writeFileSync(
|
|
29659
29807
|
validationPath(checkoutDir, record2.treeHash),
|
|
29660
29808
|
`${JSON.stringify(record2, null, 2)}
|
|
29661
29809
|
`
|
|
29662
29810
|
);
|
|
29663
29811
|
}
|
|
29664
29812
|
function ensureValidationsIgnored(checkoutDir) {
|
|
29665
|
-
const harDir =
|
|
29666
|
-
if (!
|
|
29667
|
-
const gitignorePath =
|
|
29668
|
-
const content =
|
|
29813
|
+
const harDir = path32.join(checkoutDir, ".har");
|
|
29814
|
+
if (!fs32.existsSync(harDir)) return;
|
|
29815
|
+
const gitignorePath = path32.join(harDir, ".gitignore");
|
|
29816
|
+
const content = fs32.existsSync(gitignorePath) ? fs32.readFileSync(gitignorePath, "utf8") : "";
|
|
29669
29817
|
if (content.split("\n").some((line) => line.trim() === `${VALIDATIONS_DIR}/`)) return;
|
|
29670
29818
|
const suffix = content.length === 0 || content.endsWith("\n") ? "" : "\n";
|
|
29671
|
-
|
|
29819
|
+
fs32.writeFileSync(gitignorePath, `${content}${suffix}${VALIDATIONS_DIR}/
|
|
29672
29820
|
`);
|
|
29673
29821
|
}
|
|
29674
29822
|
function recordValidation(input) {
|
|
@@ -29685,8 +29833,8 @@ function recordValidation(input) {
|
|
|
29685
29833
|
treeHash: snapshot.treeHash,
|
|
29686
29834
|
headSha: snapshot.headSha,
|
|
29687
29835
|
branch: snapshot.branch,
|
|
29688
|
-
workDir:
|
|
29689
|
-
harnessRoot:
|
|
29836
|
+
workDir: path32.resolve(input.checkoutDir),
|
|
29837
|
+
harnessRoot: path32.resolve(input.harnessRoot),
|
|
29690
29838
|
agentId: input.agentId,
|
|
29691
29839
|
status: input.status,
|
|
29692
29840
|
full: input.full,
|
|
@@ -29698,16 +29846,16 @@ function recordValidation(input) {
|
|
|
29698
29846
|
committedAt: existing?.committedAt
|
|
29699
29847
|
});
|
|
29700
29848
|
writeRecord(input.checkoutDir, record2);
|
|
29701
|
-
if (
|
|
29849
|
+
if (path32.resolve(input.harnessRoot) !== path32.resolve(input.checkoutDir)) {
|
|
29702
29850
|
writeRecord(input.harnessRoot, record2);
|
|
29703
29851
|
}
|
|
29704
29852
|
return record2;
|
|
29705
29853
|
}
|
|
29706
29854
|
function findValidation(checkoutDir, treeHash) {
|
|
29707
29855
|
const file = validationPath(checkoutDir, treeHash);
|
|
29708
|
-
if (!
|
|
29856
|
+
if (!fs32.existsSync(file)) return void 0;
|
|
29709
29857
|
try {
|
|
29710
|
-
return ValidationRecordSchema.parse(JSON.parse(
|
|
29858
|
+
return ValidationRecordSchema.parse(JSON.parse(fs32.readFileSync(file, "utf8")));
|
|
29711
29859
|
} catch {
|
|
29712
29860
|
return void 0;
|
|
29713
29861
|
}
|
|
@@ -29722,19 +29870,19 @@ function attachCommit(checkoutDir, treeHash, commitSha) {
|
|
|
29722
29870
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
29723
29871
|
};
|
|
29724
29872
|
writeRecord(checkoutDir, updated);
|
|
29725
|
-
if (record2.harnessRoot &&
|
|
29873
|
+
if (record2.harnessRoot && path32.resolve(record2.harnessRoot) !== path32.resolve(checkoutDir) && fs32.existsSync(path32.join(record2.harnessRoot, ".har"))) {
|
|
29726
29874
|
writeRecord(record2.harnessRoot, updated);
|
|
29727
29875
|
}
|
|
29728
29876
|
return updated;
|
|
29729
29877
|
}
|
|
29730
29878
|
function listValidations(harnessRoot) {
|
|
29731
29879
|
const dir = getValidationsDir(harnessRoot);
|
|
29732
|
-
if (!
|
|
29880
|
+
if (!fs32.existsSync(dir)) return [];
|
|
29733
29881
|
const records = [];
|
|
29734
|
-
for (const entry of
|
|
29882
|
+
for (const entry of fs32.readdirSync(dir)) {
|
|
29735
29883
|
if (!entry.endsWith(".json")) continue;
|
|
29736
29884
|
try {
|
|
29737
|
-
records.push(ValidationRecordSchema.parse(JSON.parse(
|
|
29885
|
+
records.push(ValidationRecordSchema.parse(JSON.parse(fs32.readFileSync(path32.join(dir, entry), "utf8"))));
|
|
29738
29886
|
} catch {
|
|
29739
29887
|
}
|
|
29740
29888
|
}
|
|
@@ -29763,6 +29911,396 @@ function createRemoteExecutor(apiUrl, apiKey) {
|
|
|
29763
29911
|
return new RemoteExecutor(url, key);
|
|
29764
29912
|
}
|
|
29765
29913
|
|
|
29914
|
+
// src/core/telemetry-config.ts
|
|
29915
|
+
var fs33 = __toESM(require("fs"));
|
|
29916
|
+
var os9 = __toESM(require("os"));
|
|
29917
|
+
var path33 = __toESM(require("path"));
|
|
29918
|
+
function getPreferencePath() {
|
|
29919
|
+
if (process.env.HAR_TELEMETRY_CONFIG_PATH) {
|
|
29920
|
+
return path33.resolve(process.env.HAR_TELEMETRY_CONFIG_PATH);
|
|
29921
|
+
}
|
|
29922
|
+
return path33.join(os9.homedir(), ".har", "telemetry.json");
|
|
29923
|
+
}
|
|
29924
|
+
function parseEnvOverride(raw) {
|
|
29925
|
+
if (raw === void 0 || raw === "") return void 0;
|
|
29926
|
+
const normalized = raw.trim().toLowerCase();
|
|
29927
|
+
if (["0", "false", "off", "no", "disabled"].includes(normalized)) return false;
|
|
29928
|
+
if (["1", "true", "on", "yes", "enabled"].includes(normalized)) return true;
|
|
29929
|
+
return void 0;
|
|
29930
|
+
}
|
|
29931
|
+
function readTelemetryPreference() {
|
|
29932
|
+
const preferencePath = getPreferencePath();
|
|
29933
|
+
try {
|
|
29934
|
+
if (!fs33.existsSync(preferencePath)) {
|
|
29935
|
+
return { enabled: true };
|
|
29936
|
+
}
|
|
29937
|
+
const parsed = JSON.parse(fs33.readFileSync(preferencePath, "utf8"));
|
|
29938
|
+
return {
|
|
29939
|
+
enabled: parsed.enabled !== false,
|
|
29940
|
+
updatedAt: typeof parsed.updatedAt === "string" ? parsed.updatedAt : void 0
|
|
29941
|
+
};
|
|
29942
|
+
} catch {
|
|
29943
|
+
return { enabled: true };
|
|
29944
|
+
}
|
|
29945
|
+
}
|
|
29946
|
+
function writeTelemetryPreference(enabled) {
|
|
29947
|
+
const preference = {
|
|
29948
|
+
enabled,
|
|
29949
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
29950
|
+
};
|
|
29951
|
+
const preferencePath = getPreferencePath();
|
|
29952
|
+
fs33.mkdirSync(path33.dirname(preferencePath), { recursive: true });
|
|
29953
|
+
fs33.writeFileSync(preferencePath, JSON.stringify(preference, null, 2) + "\n");
|
|
29954
|
+
return preference;
|
|
29955
|
+
}
|
|
29956
|
+
function isTelemetryEnabled() {
|
|
29957
|
+
const envOverride = parseEnvOverride(process.env.HAR_TELEMETRY);
|
|
29958
|
+
if (envOverride !== void 0) return envOverride;
|
|
29959
|
+
return readTelemetryPreference().enabled;
|
|
29960
|
+
}
|
|
29961
|
+
function getTelemetryPreferencePath() {
|
|
29962
|
+
return getPreferencePath();
|
|
29963
|
+
}
|
|
29964
|
+
var TELEMETRY_SIGNALS = [
|
|
29965
|
+
"Claude Code: tokens (input/output/cache) and estimated USD cost via OTEL metrics",
|
|
29966
|
+
"Codex CLI: token usage via OTEL metrics (no native USD; harvest fills gaps)",
|
|
29967
|
+
"Fallback: har control sync harvests local Claude/Codex session files when OTEL is missing"
|
|
29968
|
+
];
|
|
29969
|
+
|
|
29970
|
+
// src/core/usage-harvest/claude.ts
|
|
29971
|
+
var fs35 = __toESM(require("fs"));
|
|
29972
|
+
var os10 = __toESM(require("os"));
|
|
29973
|
+
var path35 = __toESM(require("path"));
|
|
29974
|
+
|
|
29975
|
+
// src/core/telemetry-env.ts
|
|
29976
|
+
var fs34 = __toESM(require("fs"));
|
|
29977
|
+
var path34 = __toESM(require("path"));
|
|
29978
|
+
function escapeAttrValue(value) {
|
|
29979
|
+
return value.replace(/[,=]/g, "_");
|
|
29980
|
+
}
|
|
29981
|
+
function buildOtelResourceAttributes(attrs) {
|
|
29982
|
+
const parts = [
|
|
29983
|
+
`har.session_key=${escapeAttrValue(attrs.sessionKey)}`,
|
|
29984
|
+
`har.agent_id=${attrs.agentId}`,
|
|
29985
|
+
`har.repo_path=${escapeAttrValue(attrs.repoPath)}`,
|
|
29986
|
+
`har.work_dir=${escapeAttrValue(attrs.workDir)}`
|
|
29987
|
+
];
|
|
29988
|
+
if (attrs.branch) parts.push(`har.branch=${escapeAttrValue(attrs.branch)}`);
|
|
29989
|
+
if (attrs.suffix) parts.push(`har.suffix=${escapeAttrValue(attrs.suffix)}`);
|
|
29990
|
+
if (attrs.purpose) parts.push(`har.purpose=${escapeAttrValue(attrs.purpose)}`);
|
|
29991
|
+
return parts.join(",");
|
|
29992
|
+
}
|
|
29993
|
+
function buildSessionKey(input) {
|
|
29994
|
+
if (input.branch) return input.branch;
|
|
29995
|
+
const stamp = (input.createdAt ?? (/* @__PURE__ */ new Date()).toISOString()).replace(/[:.]/g, "-");
|
|
29996
|
+
return `agent-${input.agentId}-${input.suffix ?? stamp}`;
|
|
29997
|
+
}
|
|
29998
|
+
function buildTelemetryEnvBlock(attrs, options) {
|
|
29999
|
+
const apiUrl = getControlApiUrl().replace(/\/$/, "");
|
|
30000
|
+
const lines = [
|
|
30001
|
+
"",
|
|
30002
|
+
"# HAR session attribution (generated)",
|
|
30003
|
+
`HAR_SESSION_KEY=${attrs.sessionKey}`,
|
|
30004
|
+
`HAR_CONTROL_API_URL=${apiUrl}`,
|
|
30005
|
+
`OTEL_RESOURCE_ATTRIBUTES=${buildOtelResourceAttributes(attrs)}`
|
|
30006
|
+
];
|
|
30007
|
+
const injectOtel = isTelemetryEnabled() && options?.otelReady !== false;
|
|
30008
|
+
if (injectOtel) {
|
|
30009
|
+
lines.push(
|
|
30010
|
+
"# HAR telemetry \u2192 Mission Control OTLP (disable: har telemetry off)",
|
|
30011
|
+
"CLAUDE_CODE_ENABLE_TELEMETRY=1",
|
|
30012
|
+
"OTEL_METRICS_EXPORTER=otlp",
|
|
30013
|
+
"OTEL_EXPORTER_OTLP_PROTOCOL=http/json",
|
|
30014
|
+
`OTEL_EXPORTER_OTLP_ENDPOINT=${apiUrl}/api/otel`
|
|
30015
|
+
);
|
|
30016
|
+
}
|
|
30017
|
+
return lines.join("\n") + "\n";
|
|
30018
|
+
}
|
|
30019
|
+
var TELEMETRY_MARKER_START = "# HAR session attribution (generated)";
|
|
30020
|
+
var TELEMETRY_MARKER_END = "# end HAR telemetry";
|
|
30021
|
+
function appendTelemetryEnvToFile(envFilePath, attrs, options) {
|
|
30022
|
+
const block = buildTelemetryEnvBlock(attrs, options).trimEnd() + `
|
|
30023
|
+
${TELEMETRY_MARKER_END}
|
|
30024
|
+
`;
|
|
30025
|
+
let existing = "";
|
|
30026
|
+
if (fs34.existsSync(envFilePath)) {
|
|
30027
|
+
existing = fs34.readFileSync(envFilePath, "utf8");
|
|
30028
|
+
const start = existing.indexOf(TELEMETRY_MARKER_START);
|
|
30029
|
+
if (start >= 0) {
|
|
30030
|
+
const end = existing.indexOf(TELEMETRY_MARKER_END, start);
|
|
30031
|
+
if (end >= 0) {
|
|
30032
|
+
existing = existing.slice(0, start).replace(/\n+$/, "\n") + existing.slice(end + TELEMETRY_MARKER_END.length).replace(/^\n+/, "\n");
|
|
30033
|
+
} else {
|
|
30034
|
+
existing = existing.slice(0, start).replace(/\n+$/, "\n");
|
|
30035
|
+
}
|
|
30036
|
+
}
|
|
30037
|
+
}
|
|
30038
|
+
fs34.mkdirSync(path34.dirname(envFilePath), { recursive: true });
|
|
30039
|
+
const combined = (existing.replace(/\n+$/, "\n") + block).replace(/^\n+/, "");
|
|
30040
|
+
fs34.writeFileSync(envFilePath, combined.endsWith("\n") ? combined : combined + "\n");
|
|
30041
|
+
}
|
|
30042
|
+
function buildCodexOtelSnippet(attrs) {
|
|
30043
|
+
const apiUrl = getControlApiUrl().replace(/\/$/, "");
|
|
30044
|
+
const resource = buildOtelResourceAttributes(attrs);
|
|
30045
|
+
return `# Merge into ~/.codex/config.toml (HAR does not overwrite your config)
|
|
30046
|
+
# Generated for session ${attrs.sessionKey}
|
|
30047
|
+
|
|
30048
|
+
[otel]
|
|
30049
|
+
exporter = "otlp-http"
|
|
30050
|
+
endpoint = "${apiUrl}/api/otel"
|
|
30051
|
+
metrics_exporter = "otlp-http"
|
|
30052
|
+
|
|
30053
|
+
# Optional: set resource attributes via environment when starting Codex:
|
|
30054
|
+
# export OTEL_RESOURCE_ATTRIBUTES="${resource}"
|
|
30055
|
+
# export HAR_SESSION_KEY="${attrs.sessionKey}"
|
|
30056
|
+
`;
|
|
30057
|
+
}
|
|
30058
|
+
|
|
30059
|
+
// src/core/usage-harvest/claude.ts
|
|
30060
|
+
function pathsMatch(candidate, targets) {
|
|
30061
|
+
const norm = path35.resolve(candidate);
|
|
30062
|
+
return targets.some((t2) => {
|
|
30063
|
+
const target = path35.resolve(t2);
|
|
30064
|
+
return norm === target || norm.startsWith(target + path35.sep) || target.startsWith(norm + path35.sep);
|
|
30065
|
+
});
|
|
30066
|
+
}
|
|
30067
|
+
function claudeProjectsRoot() {
|
|
30068
|
+
return process.env.HAR_CLAUDE_PROJECTS_DIR ? path35.resolve(process.env.HAR_CLAUDE_PROJECTS_DIR) : path35.join(os10.homedir(), ".claude", "projects");
|
|
30069
|
+
}
|
|
30070
|
+
function encodeClaudeProjectDir(cwd) {
|
|
30071
|
+
return cwd.replace(/[^a-zA-Z0-9]/g, "-");
|
|
30072
|
+
}
|
|
30073
|
+
function readJsonlRecords(filePath) {
|
|
30074
|
+
if (!fs35.existsSync(filePath)) return [];
|
|
30075
|
+
const lines = fs35.readFileSync(filePath, "utf8").split(/\r?\n/);
|
|
30076
|
+
const out = [];
|
|
30077
|
+
for (const line of lines) {
|
|
30078
|
+
const trimmed = line.trim();
|
|
30079
|
+
if (!trimmed.startsWith("{")) continue;
|
|
30080
|
+
try {
|
|
30081
|
+
out.push(JSON.parse(trimmed));
|
|
30082
|
+
} catch {
|
|
30083
|
+
}
|
|
30084
|
+
}
|
|
30085
|
+
return out;
|
|
30086
|
+
}
|
|
30087
|
+
function extractClaudeUsageFromRecords(records) {
|
|
30088
|
+
let tokensInput = 0;
|
|
30089
|
+
let tokensOutput = 0;
|
|
30090
|
+
let tokensCacheRead = 0;
|
|
30091
|
+
let tokensCacheCreation = 0;
|
|
30092
|
+
let costUsd = null;
|
|
30093
|
+
for (const record2 of records) {
|
|
30094
|
+
if (!record2 || typeof record2 !== "object") continue;
|
|
30095
|
+
const payload = record2;
|
|
30096
|
+
if (payload.type === "result") {
|
|
30097
|
+
const usage2 = payload.usage ?? {};
|
|
30098
|
+
tokensInput = Number(usage2.input_tokens ?? 0) + Number(usage2.cache_read_input_tokens ?? 0);
|
|
30099
|
+
tokensOutput = Number(usage2.output_tokens ?? 0);
|
|
30100
|
+
tokensCacheRead = Number(usage2.cache_read_input_tokens ?? 0);
|
|
30101
|
+
tokensCacheCreation = Number(usage2.cache_creation_input_tokens ?? 0);
|
|
30102
|
+
if (payload.total_cost_usd != null) costUsd = Number(payload.total_cost_usd);
|
|
30103
|
+
}
|
|
30104
|
+
const nestedUsage = payload.usage ?? payload.message?.usage;
|
|
30105
|
+
if (nestedUsage && payload.type !== "result") {
|
|
30106
|
+
tokensInput += Number(nestedUsage.input_tokens ?? 0);
|
|
30107
|
+
tokensOutput += Number(nestedUsage.output_tokens ?? 0);
|
|
30108
|
+
tokensCacheRead += Number(nestedUsage.cache_read_input_tokens ?? 0);
|
|
30109
|
+
tokensCacheCreation += Number(nestedUsage.cache_creation_input_tokens ?? 0);
|
|
30110
|
+
}
|
|
30111
|
+
}
|
|
30112
|
+
return { tokensInput, tokensOutput, tokensCacheRead, tokensCacheCreation, costUsd };
|
|
30113
|
+
}
|
|
30114
|
+
function harvestClaudeUsage(slot) {
|
|
30115
|
+
const targets = [slot.workDir, slot.worktreePath].filter(Boolean);
|
|
30116
|
+
if (targets.length === 0) return null;
|
|
30117
|
+
const root = claudeProjectsRoot();
|
|
30118
|
+
if (!fs35.existsSync(root)) return null;
|
|
30119
|
+
const sessionKey = buildSessionKey({
|
|
30120
|
+
branch: slot.branch,
|
|
30121
|
+
agentId: slot.agentId,
|
|
30122
|
+
suffix: slot.suffix,
|
|
30123
|
+
createdAt: slot.sessionCreatedAt
|
|
30124
|
+
});
|
|
30125
|
+
let best = null;
|
|
30126
|
+
let bestMtime = 0;
|
|
30127
|
+
for (const entry of fs35.readdirSync(root, { withFileTypes: true })) {
|
|
30128
|
+
if (!entry.isDirectory()) continue;
|
|
30129
|
+
const projectDir = path35.join(root, entry.name);
|
|
30130
|
+
const encodedHit = targets.some((t2) => entry.name.includes(encodeClaudeProjectDir(t2).slice(0, 40)));
|
|
30131
|
+
for (const file of fs35.readdirSync(projectDir)) {
|
|
30132
|
+
if (!file.endsWith(".jsonl")) continue;
|
|
30133
|
+
const filePath = path35.join(projectDir, file);
|
|
30134
|
+
const stat = fs35.statSync(filePath);
|
|
30135
|
+
const records = readJsonlRecords(filePath);
|
|
30136
|
+
let cwdHit = encodedHit;
|
|
30137
|
+
for (const record2 of records) {
|
|
30138
|
+
if (!record2 || typeof record2 !== "object") continue;
|
|
30139
|
+
const cwd = String(record2.cwd ?? "");
|
|
30140
|
+
if (cwd && pathsMatch(cwd, targets)) {
|
|
30141
|
+
cwdHit = true;
|
|
30142
|
+
break;
|
|
30143
|
+
}
|
|
30144
|
+
}
|
|
30145
|
+
if (!cwdHit) continue;
|
|
30146
|
+
const usage2 = extractClaudeUsageFromRecords(records);
|
|
30147
|
+
if (usage2.tokensInput + usage2.tokensOutput + usage2.tokensCacheRead === 0 && (usage2.costUsd == null || usage2.costUsd === 0)) {
|
|
30148
|
+
continue;
|
|
30149
|
+
}
|
|
30150
|
+
if (stat.mtimeMs >= bestMtime) {
|
|
30151
|
+
bestMtime = stat.mtimeMs;
|
|
30152
|
+
best = usage2;
|
|
30153
|
+
}
|
|
30154
|
+
}
|
|
30155
|
+
}
|
|
30156
|
+
if (!best) return null;
|
|
30157
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
30158
|
+
const tokensTotal = best.tokensInput + best.tokensOutput + best.tokensCacheRead + best.tokensCacheCreation;
|
|
30159
|
+
return {
|
|
30160
|
+
sessionKey,
|
|
30161
|
+
agentId: slot.agentId,
|
|
30162
|
+
agentTool: "claude_code",
|
|
30163
|
+
workDir: slot.workDir,
|
|
30164
|
+
branch: slot.branch,
|
|
30165
|
+
suffix: slot.suffix,
|
|
30166
|
+
tokensInput: best.tokensInput,
|
|
30167
|
+
tokensOutput: best.tokensOutput,
|
|
30168
|
+
tokensCacheRead: best.tokensCacheRead,
|
|
30169
|
+
tokensCacheCreation: best.tokensCacheCreation,
|
|
30170
|
+
tokensTotal,
|
|
30171
|
+
costUsd: best.costUsd,
|
|
30172
|
+
sources: ["harvest"],
|
|
30173
|
+
firstSeenAt: slot.sessionCreatedAt ?? now,
|
|
30174
|
+
lastSeenAt: now
|
|
30175
|
+
};
|
|
30176
|
+
}
|
|
30177
|
+
|
|
30178
|
+
// src/core/usage-harvest/codex.ts
|
|
30179
|
+
var fs36 = __toESM(require("fs"));
|
|
30180
|
+
var os11 = __toESM(require("os"));
|
|
30181
|
+
var path36 = __toESM(require("path"));
|
|
30182
|
+
function pathsMatch2(candidate, targets) {
|
|
30183
|
+
const norm = path36.resolve(candidate);
|
|
30184
|
+
return targets.some((t2) => {
|
|
30185
|
+
const target = path36.resolve(t2);
|
|
30186
|
+
return norm === target || norm.startsWith(target + path36.sep) || target.startsWith(norm + path36.sep);
|
|
30187
|
+
});
|
|
30188
|
+
}
|
|
30189
|
+
function codexSessionsRoot() {
|
|
30190
|
+
return process.env.HAR_CODEX_SESSIONS_DIR ? path36.resolve(process.env.HAR_CODEX_SESSIONS_DIR) : path36.join(os11.homedir(), ".codex", "sessions");
|
|
30191
|
+
}
|
|
30192
|
+
function walkJsonlFiles(dir, out = []) {
|
|
30193
|
+
if (!fs36.existsSync(dir)) return out;
|
|
30194
|
+
for (const entry of fs36.readdirSync(dir, { withFileTypes: true })) {
|
|
30195
|
+
const full = path36.join(dir, entry.name);
|
|
30196
|
+
if (entry.isDirectory()) walkJsonlFiles(full, out);
|
|
30197
|
+
else if (entry.isFile() && entry.name.endsWith(".jsonl")) out.push(full);
|
|
30198
|
+
}
|
|
30199
|
+
return out;
|
|
30200
|
+
}
|
|
30201
|
+
function readJsonl(filePath) {
|
|
30202
|
+
const lines = fs36.readFileSync(filePath, "utf8").split(/\r?\n/);
|
|
30203
|
+
const out = [];
|
|
30204
|
+
for (const line of lines) {
|
|
30205
|
+
const trimmed = line.trim();
|
|
30206
|
+
if (!trimmed.startsWith("{")) continue;
|
|
30207
|
+
try {
|
|
30208
|
+
out.push(JSON.parse(trimmed));
|
|
30209
|
+
} catch {
|
|
30210
|
+
}
|
|
30211
|
+
}
|
|
30212
|
+
return out;
|
|
30213
|
+
}
|
|
30214
|
+
function extractCodexTokens(records) {
|
|
30215
|
+
let tokensInput = 0;
|
|
30216
|
+
let tokensOutput = 0;
|
|
30217
|
+
let tokensCacheRead = 0;
|
|
30218
|
+
let cwd;
|
|
30219
|
+
for (const record2 of records) {
|
|
30220
|
+
if (!record2 || typeof record2 !== "object") continue;
|
|
30221
|
+
const payload = record2;
|
|
30222
|
+
if (typeof payload.cwd === "string") cwd = payload.cwd;
|
|
30223
|
+
if (typeof payload.working_directory === "string") {
|
|
30224
|
+
cwd = payload.working_directory;
|
|
30225
|
+
}
|
|
30226
|
+
const usage2 = payload.usage ?? payload.token_usage ?? payload.event?.usage;
|
|
30227
|
+
if (usage2) {
|
|
30228
|
+
tokensInput += Number(
|
|
30229
|
+
usage2.input_tokens ?? usage2.input_token_count ?? usage2.input ?? 0
|
|
30230
|
+
);
|
|
30231
|
+
tokensOutput += Number(
|
|
30232
|
+
usage2.output_tokens ?? usage2.output_token_count ?? usage2.output ?? 0
|
|
30233
|
+
);
|
|
30234
|
+
tokensCacheRead += Number(
|
|
30235
|
+
usage2.cached_input_tokens ?? usage2.cached_token_count ?? usage2.cache_read_input_tokens ?? 0
|
|
30236
|
+
);
|
|
30237
|
+
tokensOutput += Number(usage2.reasoning_tokens ?? usage2.reasoning_token_count ?? 0);
|
|
30238
|
+
}
|
|
30239
|
+
if (payload.type === "response.completed" || payload["event.kind"] === "response.completed") {
|
|
30240
|
+
tokensInput = Math.max(tokensInput, Number(payload.input_token_count ?? 0));
|
|
30241
|
+
tokensOutput = Math.max(tokensOutput, Number(payload.output_token_count ?? 0));
|
|
30242
|
+
tokensCacheRead = Math.max(tokensCacheRead, Number(payload.cached_token_count ?? 0));
|
|
30243
|
+
}
|
|
30244
|
+
}
|
|
30245
|
+
return { tokensInput, tokensOutput, tokensCacheRead, cwd };
|
|
30246
|
+
}
|
|
30247
|
+
function harvestCodexUsage(slot) {
|
|
30248
|
+
const targets = [slot.workDir, slot.worktreePath].filter(Boolean);
|
|
30249
|
+
if (targets.length === 0) return null;
|
|
30250
|
+
const root = codexSessionsRoot();
|
|
30251
|
+
const files = walkJsonlFiles(root);
|
|
30252
|
+
if (files.length === 0) return null;
|
|
30253
|
+
const sessionKey = buildSessionKey({
|
|
30254
|
+
branch: slot.branch,
|
|
30255
|
+
agentId: slot.agentId,
|
|
30256
|
+
suffix: slot.suffix,
|
|
30257
|
+
createdAt: slot.sessionCreatedAt
|
|
30258
|
+
});
|
|
30259
|
+
let best = null;
|
|
30260
|
+
let bestMtime = 0;
|
|
30261
|
+
for (const file of files) {
|
|
30262
|
+
const records = readJsonl(file);
|
|
30263
|
+
const extracted = extractCodexTokens(records);
|
|
30264
|
+
if (!extracted.cwd || !pathsMatch2(extracted.cwd, targets)) continue;
|
|
30265
|
+
if (extracted.tokensInput + extracted.tokensOutput + extracted.tokensCacheRead === 0) continue;
|
|
30266
|
+
const mtime = fs36.statSync(file).mtimeMs;
|
|
30267
|
+
if (mtime >= bestMtime) {
|
|
30268
|
+
bestMtime = mtime;
|
|
30269
|
+
best = extracted;
|
|
30270
|
+
}
|
|
30271
|
+
}
|
|
30272
|
+
if (!best) return null;
|
|
30273
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
30274
|
+
const tokensTotal = best.tokensInput + best.tokensOutput + best.tokensCacheRead;
|
|
30275
|
+
return {
|
|
30276
|
+
sessionKey,
|
|
30277
|
+
agentId: slot.agentId,
|
|
30278
|
+
agentTool: "codex",
|
|
30279
|
+
workDir: slot.workDir,
|
|
30280
|
+
branch: slot.branch,
|
|
30281
|
+
suffix: slot.suffix,
|
|
30282
|
+
tokensInput: best.tokensInput,
|
|
30283
|
+
tokensOutput: best.tokensOutput,
|
|
30284
|
+
tokensCacheRead: best.tokensCacheRead,
|
|
30285
|
+
tokensCacheCreation: 0,
|
|
30286
|
+
tokensTotal,
|
|
30287
|
+
costUsd: null,
|
|
30288
|
+
sources: ["harvest"],
|
|
30289
|
+
firstSeenAt: slot.sessionCreatedAt ?? now,
|
|
30290
|
+
lastSeenAt: now
|
|
30291
|
+
};
|
|
30292
|
+
}
|
|
30293
|
+
|
|
30294
|
+
// src/core/usage-harvest/index.ts
|
|
30295
|
+
function harvestUsageForSlot(slot) {
|
|
30296
|
+
const out = [];
|
|
30297
|
+
const claude = harvestClaudeUsage(slot);
|
|
30298
|
+
if (claude) out.push(claude);
|
|
30299
|
+
const codex = harvestCodexUsage(slot);
|
|
30300
|
+
if (codex) out.push(codex);
|
|
30301
|
+
return out;
|
|
30302
|
+
}
|
|
30303
|
+
|
|
29766
30304
|
// src/core/control-sync.ts
|
|
29767
30305
|
async function postJson(url, body, dryRun) {
|
|
29768
30306
|
if (dryRun) return null;
|
|
@@ -29792,7 +30330,7 @@ async function waitForControlApi(apiUrl = getControlApiUrl(), timeoutMs = 6e4, i
|
|
|
29792
30330
|
const deadline = Date.now() + timeoutMs;
|
|
29793
30331
|
while (Date.now() < deadline) {
|
|
29794
30332
|
if (await isControlApiReachable(apiUrl)) return true;
|
|
29795
|
-
await new Promise((
|
|
30333
|
+
await new Promise((resolve33) => setTimeout(resolve33, intervalMs));
|
|
29796
30334
|
}
|
|
29797
30335
|
return false;
|
|
29798
30336
|
}
|
|
@@ -29804,7 +30342,7 @@ async function syncAllKnownReposWithControl(options) {
|
|
|
29804
30342
|
}
|
|
29805
30343
|
const repoPaths = new Set(listRegisteredRepos());
|
|
29806
30344
|
if (options?.cwd) {
|
|
29807
|
-
const cwd =
|
|
30345
|
+
const cwd = path37.resolve(options.cwd);
|
|
29808
30346
|
if (readManifest(cwd)) repoPaths.add(cwd);
|
|
29809
30347
|
}
|
|
29810
30348
|
try {
|
|
@@ -29812,7 +30350,7 @@ async function syncAllKnownReposWithControl(options) {
|
|
|
29812
30350
|
if (listResponse.ok) {
|
|
29813
30351
|
const repos = await listResponse.json();
|
|
29814
30352
|
for (const repo of repos) {
|
|
29815
|
-
const resolved =
|
|
30353
|
+
const resolved = path37.resolve(repo.path);
|
|
29816
30354
|
if (readManifest(resolved)) repoPaths.add(resolved);
|
|
29817
30355
|
}
|
|
29818
30356
|
}
|
|
@@ -29831,7 +30369,7 @@ async function syncAllKnownReposWithControl(options) {
|
|
|
29831
30369
|
return { synced, failed };
|
|
29832
30370
|
}
|
|
29833
30371
|
async function registerRepoWithControl(options) {
|
|
29834
|
-
const repoPath =
|
|
30372
|
+
const repoPath = path37.resolve(options.repoPath);
|
|
29835
30373
|
const apiUrl = options.apiUrl ?? getControlApiUrl();
|
|
29836
30374
|
const manifest = readManifest(repoPath);
|
|
29837
30375
|
const stagesRegistry = readStageRegistry(repoPath);
|
|
@@ -29844,7 +30382,7 @@ async function registerRepoWithControl(options) {
|
|
|
29844
30382
|
return postJson(`${apiUrl}/api/repos`, body, options.dryRun);
|
|
29845
30383
|
}
|
|
29846
30384
|
async function syncRepoWithControl(options) {
|
|
29847
|
-
const repoPath =
|
|
30385
|
+
const repoPath = path37.resolve(options.repoPath);
|
|
29848
30386
|
const apiUrl = options.apiUrl ?? getControlApiUrl();
|
|
29849
30387
|
if (options.cloud) {
|
|
29850
30388
|
const remote = createRemoteExecutor();
|
|
@@ -29872,7 +30410,7 @@ async function syncRepoWithControl(options) {
|
|
|
29872
30410
|
const listResponse = await fetch(`${apiUrl}/api/repos`);
|
|
29873
30411
|
if (!listResponse.ok) throw new Error("Failed to list repos from Control API");
|
|
29874
30412
|
const repos = await listResponse.json();
|
|
29875
|
-
const existing = repos.find((r2) =>
|
|
30413
|
+
const existing = repos.find((r2) => path37.resolve(r2.path) === repoPath);
|
|
29876
30414
|
if (!existing) throw new Error(`Repo not registered: ${repoPath}`);
|
|
29877
30415
|
await syncRepoRunsAndSlots(apiUrl, existing.id, repoPath, options.dryRun);
|
|
29878
30416
|
return;
|
|
@@ -29891,6 +30429,37 @@ async function syncRepoRunsAndSlots(apiUrl, repoId, repoPath, dryRun) {
|
|
|
29891
30429
|
generatedAt: status.generatedAt
|
|
29892
30430
|
});
|
|
29893
30431
|
await postJson(`${apiUrl}/api/repos/${repoId}/slots`, slotsBody, dryRun);
|
|
30432
|
+
if (isTelemetryEnabled() && process.env.NODE_ENV !== "test") {
|
|
30433
|
+
try {
|
|
30434
|
+
const usage2 = status.slots.flatMap(
|
|
30435
|
+
(slot) => harvestUsageForSlot({
|
|
30436
|
+
agentId: slot.agentId,
|
|
30437
|
+
workDir: slot.workDir,
|
|
30438
|
+
worktreePath: slot.worktreePath,
|
|
30439
|
+
branch: slot.branch,
|
|
30440
|
+
suffix: slot.suffix,
|
|
30441
|
+
sessionCreatedAt: slot.sessionCreatedAt,
|
|
30442
|
+
repoPath
|
|
30443
|
+
}).map((row) => ({
|
|
30444
|
+
...row,
|
|
30445
|
+
sessionKey: row.sessionKey || buildSessionKey({
|
|
30446
|
+
branch: slot.branch,
|
|
30447
|
+
agentId: slot.agentId,
|
|
30448
|
+
suffix: slot.suffix,
|
|
30449
|
+
createdAt: slot.sessionCreatedAt
|
|
30450
|
+
})
|
|
30451
|
+
}))
|
|
30452
|
+
);
|
|
30453
|
+
if (usage2.length > 0) {
|
|
30454
|
+
const usageBody = SyncUsageInputSchema.parse({ usage: usage2 });
|
|
30455
|
+
await postJson(`${apiUrl}/api/repos/${repoId}/usage`, usageBody, dryRun);
|
|
30456
|
+
}
|
|
30457
|
+
} catch (err) {
|
|
30458
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
30459
|
+
process.stderr.write(`[har control] usage harvest skipped: ${message}
|
|
30460
|
+
`);
|
|
30461
|
+
}
|
|
30462
|
+
}
|
|
29894
30463
|
const validations = listValidations(resolveHarnessRoot(repoPath));
|
|
29895
30464
|
if (validations.length > 0) {
|
|
29896
30465
|
const validationsBody = SyncValidationsInputSchema.parse({ validations });
|
|
@@ -29943,7 +30512,199 @@ function checkLaunchGuard2(repoPath, agentId, options = {}) {
|
|
|
29943
30512
|
return { allowed: true, slot: occupied.slot, readiness };
|
|
29944
30513
|
}
|
|
29945
30514
|
|
|
30515
|
+
// src/core/control-lifecycle.ts
|
|
30516
|
+
var import_child_process8 = require("child_process");
|
|
30517
|
+
var path38 = __toESM(require("path"));
|
|
30518
|
+
|
|
30519
|
+
// src/core/control-image.ts
|
|
30520
|
+
var DEFAULT_CONTROL_IMAGE = "theosfactory/har-control";
|
|
30521
|
+
function getControlImageName() {
|
|
30522
|
+
return process.env.HAR_CONTROL_IMAGE ?? DEFAULT_CONTROL_IMAGE;
|
|
30523
|
+
}
|
|
30524
|
+
function getControlImageTag() {
|
|
30525
|
+
return process.env.HAR_CONTROL_IMAGE_TAG ?? getHarPackageVersion();
|
|
30526
|
+
}
|
|
30527
|
+
function getControlImageRef() {
|
|
30528
|
+
return `${getControlImageName()}:${getControlImageTag()}`;
|
|
30529
|
+
}
|
|
30530
|
+
function shouldBuildControlLocally() {
|
|
30531
|
+
return process.env.HAR_CONTROL_BUILD === "true";
|
|
30532
|
+
}
|
|
30533
|
+
|
|
30534
|
+
// src/core/control-lifecycle.ts
|
|
30535
|
+
function resolveControlDir() {
|
|
30536
|
+
return path38.resolve(__dirname, "..", "control");
|
|
30537
|
+
}
|
|
30538
|
+
function resolveControlComposeFiles(options) {
|
|
30539
|
+
const controlDir = resolveControlDir();
|
|
30540
|
+
const files = [path38.join(controlDir, "docker-compose.yml")];
|
|
30541
|
+
if (options?.build ?? shouldBuildControlLocally()) {
|
|
30542
|
+
files.push(path38.join(controlDir, "docker-compose.build.yml"));
|
|
30543
|
+
}
|
|
30544
|
+
return files;
|
|
30545
|
+
}
|
|
30546
|
+
function buildDockerComposeEnv() {
|
|
30547
|
+
return {
|
|
30548
|
+
...process.env,
|
|
30549
|
+
HAR_CONTROL_IMAGE: getControlImageName(),
|
|
30550
|
+
HAR_CONTROL_IMAGE_TAG: getControlImageTag()
|
|
30551
|
+
};
|
|
30552
|
+
}
|
|
30553
|
+
function runDockerCompose(args, options) {
|
|
30554
|
+
const controlDir = resolveControlDir();
|
|
30555
|
+
const composeFiles = resolveControlComposeFiles(options);
|
|
30556
|
+
const composeArgs = composeFiles.flatMap((file) => ["-f", file]);
|
|
30557
|
+
const result = (0, import_child_process8.spawnSync)("docker", ["compose", ...composeArgs, ...args], {
|
|
30558
|
+
cwd: controlDir,
|
|
30559
|
+
stdio: "inherit",
|
|
30560
|
+
env: buildDockerComposeEnv()
|
|
30561
|
+
});
|
|
30562
|
+
if (result.error) {
|
|
30563
|
+
throw result.error;
|
|
30564
|
+
}
|
|
30565
|
+
return result.status ?? 1;
|
|
30566
|
+
}
|
|
30567
|
+
async function startMissionControl(options) {
|
|
30568
|
+
const build = options.build ?? shouldBuildControlLocally();
|
|
30569
|
+
const imageRef = getControlImageRef();
|
|
30570
|
+
if (!build) {
|
|
30571
|
+
const pullCode = runDockerCompose(["pull"], { build: false });
|
|
30572
|
+
if (pullCode !== 0) {
|
|
30573
|
+
return { code: pullCode, apiUrl: getControlApiUrl(), imageRef };
|
|
30574
|
+
}
|
|
30575
|
+
}
|
|
30576
|
+
const detach = options.detach !== false;
|
|
30577
|
+
const upArgs = detach ? ["up", "-d"] : ["up"];
|
|
30578
|
+
if (build) {
|
|
30579
|
+
upArgs.push("--build");
|
|
30580
|
+
}
|
|
30581
|
+
const code = runDockerCompose(upArgs, { build });
|
|
30582
|
+
return { code, apiUrl: getControlApiUrl(), imageRef };
|
|
30583
|
+
}
|
|
30584
|
+
async function syncReposAfterControlStart(cwd) {
|
|
30585
|
+
if (!isControlEnabled()) {
|
|
30586
|
+
return { synced: 0, failed: 0, apiReady: false };
|
|
30587
|
+
}
|
|
30588
|
+
const apiUrl = getControlApiUrl();
|
|
30589
|
+
const apiReady = await waitForControlApi(apiUrl);
|
|
30590
|
+
if (!apiReady) {
|
|
30591
|
+
return { synced: 0, failed: 0, apiReady: false };
|
|
30592
|
+
}
|
|
30593
|
+
const { synced, failed } = await syncAllKnownReposWithControl({ apiUrl, cwd });
|
|
30594
|
+
return { synced, failed, apiReady: true };
|
|
30595
|
+
}
|
|
30596
|
+
async function startControlAndSync(options) {
|
|
30597
|
+
const { code, apiUrl, imageRef } = await startMissionControl({
|
|
30598
|
+
detach: options?.detach,
|
|
30599
|
+
build: options?.build
|
|
30600
|
+
});
|
|
30601
|
+
if (code !== 0) {
|
|
30602
|
+
return { code, apiUrl, imageRef, synced: 0, failed: 0, apiReady: false };
|
|
30603
|
+
}
|
|
30604
|
+
const { synced, failed, apiReady } = await syncReposAfterControlStart(options?.cwd ?? process.cwd());
|
|
30605
|
+
return { code, apiUrl, imageRef, synced, failed, apiReady };
|
|
30606
|
+
}
|
|
30607
|
+
|
|
30608
|
+
// src/core/telemetry-ensure.ts
|
|
30609
|
+
async function ensureTelemetryInfrastructure(options) {
|
|
30610
|
+
const apiUrl = getControlApiUrl();
|
|
30611
|
+
const telemetryEnabled = isTelemetryEnabled();
|
|
30612
|
+
const controlEnabled = isControlEnabled();
|
|
30613
|
+
const startIfNeeded = options?.startIfNeeded !== false;
|
|
30614
|
+
if (!telemetryEnabled) {
|
|
30615
|
+
return {
|
|
30616
|
+
telemetryEnabled: false,
|
|
30617
|
+
controlEnabled,
|
|
30618
|
+
apiUrl,
|
|
30619
|
+
reachable: false,
|
|
30620
|
+
started: false,
|
|
30621
|
+
otelReady: false,
|
|
30622
|
+
message: "Telemetry is off \u2014 Mission Control will not be auto-started. Enable with: har telemetry on"
|
|
30623
|
+
};
|
|
30624
|
+
}
|
|
30625
|
+
if (!controlEnabled) {
|
|
30626
|
+
return {
|
|
30627
|
+
telemetryEnabled: true,
|
|
30628
|
+
controlEnabled: false,
|
|
30629
|
+
apiUrl,
|
|
30630
|
+
reachable: false,
|
|
30631
|
+
started: false,
|
|
30632
|
+
otelReady: false,
|
|
30633
|
+
warning: "Telemetry is on but HAR_CONTROL_DISABLED=true \u2014 cannot start Mission Control. Unset it or run har control up manually."
|
|
30634
|
+
};
|
|
30635
|
+
}
|
|
30636
|
+
if (await isControlApiReachable(apiUrl)) {
|
|
30637
|
+
return {
|
|
30638
|
+
telemetryEnabled: true,
|
|
30639
|
+
controlEnabled: true,
|
|
30640
|
+
apiUrl,
|
|
30641
|
+
reachable: true,
|
|
30642
|
+
started: false,
|
|
30643
|
+
otelReady: true,
|
|
30644
|
+
message: `Telemetry on \u2014 Mission Control reachable at ${apiUrl}`
|
|
30645
|
+
};
|
|
30646
|
+
}
|
|
30647
|
+
if (!startIfNeeded || process.env.NODE_ENV === "test") {
|
|
30648
|
+
return {
|
|
30649
|
+
telemetryEnabled: true,
|
|
30650
|
+
controlEnabled: true,
|
|
30651
|
+
apiUrl,
|
|
30652
|
+
reachable: false,
|
|
30653
|
+
started: false,
|
|
30654
|
+
otelReady: false,
|
|
30655
|
+
warning: `Telemetry on but Mission Control is not reachable at ${apiUrl}. Run: har control up`
|
|
30656
|
+
};
|
|
30657
|
+
}
|
|
30658
|
+
try {
|
|
30659
|
+
const { code } = await startMissionControl({ detach: true });
|
|
30660
|
+
if (code !== 0) {
|
|
30661
|
+
return {
|
|
30662
|
+
telemetryEnabled: true,
|
|
30663
|
+
controlEnabled: true,
|
|
30664
|
+
apiUrl,
|
|
30665
|
+
reachable: false,
|
|
30666
|
+
started: false,
|
|
30667
|
+
otelReady: false,
|
|
30668
|
+
warning: `Telemetry on \u2014 failed to start Mission Control (exit ${code}). OTEL export skipped; harvest may fill gaps later. Fix Docker / run: har control up`
|
|
30669
|
+
};
|
|
30670
|
+
}
|
|
30671
|
+
} catch (err) {
|
|
30672
|
+
const detail = err instanceof Error ? err.message : String(err);
|
|
30673
|
+
return {
|
|
30674
|
+
telemetryEnabled: true,
|
|
30675
|
+
controlEnabled: true,
|
|
30676
|
+
apiUrl,
|
|
30677
|
+
reachable: false,
|
|
30678
|
+
started: false,
|
|
30679
|
+
otelReady: false,
|
|
30680
|
+
warning: `Telemetry on \u2014 could not start Mission Control (${detail}). OTEL export skipped. Run: har control up`
|
|
30681
|
+
};
|
|
30682
|
+
}
|
|
30683
|
+
const ready = await waitForControlApi(apiUrl, 9e4);
|
|
30684
|
+
if (!ready) {
|
|
30685
|
+
return {
|
|
30686
|
+
telemetryEnabled: true,
|
|
30687
|
+
controlEnabled: true,
|
|
30688
|
+
apiUrl,
|
|
30689
|
+
reachable: false,
|
|
30690
|
+
started: true,
|
|
30691
|
+
otelReady: false,
|
|
30692
|
+
warning: `Telemetry on \u2014 started Mission Control but API did not become ready at ${apiUrl}. OTEL export skipped for now.`
|
|
30693
|
+
};
|
|
30694
|
+
}
|
|
30695
|
+
return {
|
|
30696
|
+
telemetryEnabled: true,
|
|
30697
|
+
controlEnabled: true,
|
|
30698
|
+
apiUrl,
|
|
30699
|
+
reachable: true,
|
|
30700
|
+
started: true,
|
|
30701
|
+
otelReady: true,
|
|
30702
|
+
message: `Telemetry on \u2014 started Mission Control at ${apiUrl}`
|
|
30703
|
+
};
|
|
30704
|
+
}
|
|
30705
|
+
|
|
29946
30706
|
// src/core/run-service.ts
|
|
30707
|
+
var path39 = __toESM(require("path"));
|
|
29947
30708
|
function extractShellOutput(result) {
|
|
29948
30709
|
const stdout = result.logs?.find((log) => log.stream === "stdout")?.content ?? result.logs?.find((log) => !log.stream)?.content ?? "";
|
|
29949
30710
|
const stderr = result.logs?.find((log) => log.stream === "stderr")?.content ?? "";
|
|
@@ -30063,6 +30824,21 @@ var RunService = class {
|
|
|
30063
30824
|
} : void 0
|
|
30064
30825
|
};
|
|
30065
30826
|
}
|
|
30827
|
+
let telemetryBanner = "";
|
|
30828
|
+
let otelReady = false;
|
|
30829
|
+
if (isTelemetryEnabled() && process.env.NODE_ENV !== "test") {
|
|
30830
|
+
const ensured = await ensureTelemetryInfrastructure({ startIfNeeded: true });
|
|
30831
|
+
otelReady = ensured.otelReady;
|
|
30832
|
+
if (ensured.message) {
|
|
30833
|
+
telemetryBanner += `${ensured.message}
|
|
30834
|
+
Usage from Claude Code / Codex will appear under Worktrees. Disable: har telemetry off
|
|
30835
|
+
`;
|
|
30836
|
+
}
|
|
30837
|
+
if (ensured.warning) {
|
|
30838
|
+
telemetryBanner += `${ensured.warning}
|
|
30839
|
+
`;
|
|
30840
|
+
}
|
|
30841
|
+
}
|
|
30066
30842
|
const result = await this.runStage({
|
|
30067
30843
|
repoPath: options.repoPath,
|
|
30068
30844
|
kind: "launch",
|
|
@@ -30086,6 +30862,32 @@ var RunService = class {
|
|
|
30086
30862
|
envResult.worktreePath = session.worktreePath;
|
|
30087
30863
|
envResult.branch = session.branch;
|
|
30088
30864
|
envResult.previewUrls = envResult.previewUrls ?? session.previewUrls;
|
|
30865
|
+
const sessionKey = buildSessionKey({
|
|
30866
|
+
branch: session.branch,
|
|
30867
|
+
agentId: options.agentId,
|
|
30868
|
+
suffix: session.suffix,
|
|
30869
|
+
createdAt: session.createdAt
|
|
30870
|
+
});
|
|
30871
|
+
const envFile = path39.join(session.workDir, `.env.agent.${options.agentId}`);
|
|
30872
|
+
try {
|
|
30873
|
+
appendTelemetryEnvToFile(
|
|
30874
|
+
envFile,
|
|
30875
|
+
{
|
|
30876
|
+
sessionKey,
|
|
30877
|
+
agentId: options.agentId,
|
|
30878
|
+
repoPath: path39.resolve(options.repoPath),
|
|
30879
|
+
workDir: session.workDir,
|
|
30880
|
+
branch: session.branch,
|
|
30881
|
+
suffix: session.suffix,
|
|
30882
|
+
purpose: session.purpose
|
|
30883
|
+
},
|
|
30884
|
+
{ otelReady: otelReady && isTelemetryEnabled() }
|
|
30885
|
+
);
|
|
30886
|
+
} catch {
|
|
30887
|
+
}
|
|
30888
|
+
}
|
|
30889
|
+
if (telemetryBanner) {
|
|
30890
|
+
envResult.stderr = `${telemetryBanner}${envResult.stderr ?? ""}`;
|
|
30089
30891
|
}
|
|
30090
30892
|
}
|
|
30091
30893
|
return envResult;
|
|
@@ -30320,11 +31122,36 @@ var envCommand = {
|
|
|
30320
31122
|
}),
|
|
30321
31123
|
handleMaintain
|
|
30322
31124
|
).command(
|
|
30323
|
-
"add-stage
|
|
30324
|
-
|
|
31125
|
+
"add-stage [template]",
|
|
31126
|
+
`Add a stage template (${STAGE_TEMPLATE_IDS.join(", ")}) or a custom stage (--custom)`,
|
|
30325
31127
|
(y2) => y2.positional("template", {
|
|
30326
31128
|
type: "string",
|
|
30327
|
-
describe:
|
|
31129
|
+
describe: `Stage template id (${STAGE_TEMPLATE_IDS.join(", ")}), or the new stage id with --custom`
|
|
31130
|
+
}).option("list", {
|
|
31131
|
+
type: "boolean",
|
|
31132
|
+
default: false,
|
|
31133
|
+
describe: "List available stage templates and exit"
|
|
31134
|
+
}).option("custom", {
|
|
31135
|
+
type: "boolean",
|
|
31136
|
+
default: false,
|
|
31137
|
+
describe: "Register a custom stage instead of a shipped template"
|
|
31138
|
+
}).option("kind", {
|
|
31139
|
+
type: "string",
|
|
31140
|
+
describe: "Custom stage kind (setup, launch, verify, test, inspect, reset, teardown, custom)"
|
|
31141
|
+
}).option("command", {
|
|
31142
|
+
type: "string",
|
|
31143
|
+
describe: 'Custom stage shell command ({agentId} is substituted), e.g. "npm test"'
|
|
31144
|
+
}).option("script", {
|
|
31145
|
+
type: "boolean",
|
|
31146
|
+
default: false,
|
|
31147
|
+
describe: "Scaffold .har/stages/<id>.sh from the contract skeleton (see .har/STAGES.md)"
|
|
31148
|
+
}).option("description", {
|
|
31149
|
+
type: "string",
|
|
31150
|
+
describe: "Custom stage description shown in the registry and Mission Control"
|
|
31151
|
+
}).option("verification", {
|
|
31152
|
+
type: "boolean",
|
|
31153
|
+
default: false,
|
|
31154
|
+
describe: "Include the custom stage in verify --full (stages.json verificationStages)"
|
|
30328
31155
|
}).option("repo", { type: "string", default: ".", describe: "Path to the repository" }).option("force", {
|
|
30329
31156
|
type: "boolean",
|
|
30330
31157
|
default: false,
|
|
@@ -30332,7 +31159,7 @@ var envCommand = {
|
|
|
30332
31159
|
}).option("skip-ci", {
|
|
30333
31160
|
type: "boolean",
|
|
30334
31161
|
default: false,
|
|
30335
|
-
describe: "Do not copy .github/workflows/playwright.yml"
|
|
31162
|
+
describe: "Do not copy optional CI workflow files (e.g. .github/workflows/playwright.yml)"
|
|
30336
31163
|
}),
|
|
30337
31164
|
handleAddStage
|
|
30338
31165
|
).command(
|
|
@@ -30427,7 +31254,7 @@ var envCommand = {
|
|
|
30427
31254
|
}
|
|
30428
31255
|
};
|
|
30429
31256
|
async function handleInit(argv) {
|
|
30430
|
-
const repoPath =
|
|
31257
|
+
const repoPath = path40.resolve(argv.repo);
|
|
30431
31258
|
header("har env init");
|
|
30432
31259
|
info(`Repository: ${repoPath}`);
|
|
30433
31260
|
try {
|
|
@@ -30486,7 +31313,7 @@ async function handleInit(argv) {
|
|
|
30486
31313
|
}
|
|
30487
31314
|
}
|
|
30488
31315
|
async function handleMaintain(argv) {
|
|
30489
|
-
const repoPath =
|
|
31316
|
+
const repoPath = path40.resolve(argv.repo);
|
|
30490
31317
|
header("har env maintain");
|
|
30491
31318
|
info(`Repository: ${repoPath}`);
|
|
30492
31319
|
try {
|
|
@@ -30582,7 +31409,7 @@ async function handleAgentMdProposal(repoPath, autoYes) {
|
|
|
30582
31409
|
if (autoYes) {
|
|
30583
31410
|
const proposal = readAgentMdProposal(repoPath);
|
|
30584
31411
|
if (proposal) {
|
|
30585
|
-
writeFileSafe(
|
|
31412
|
+
writeFileSafe(path40.join(repoPath, "AGENT.md"), proposal.content);
|
|
30586
31413
|
clearAgentMdProposal(repoPath);
|
|
30587
31414
|
info("Applied AGENT.md proposal (--yes)");
|
|
30588
31415
|
}
|
|
@@ -30591,16 +31418,64 @@ async function handleAgentMdProposal(repoPath, autoYes) {
|
|
|
30591
31418
|
await promptApplyAgentMdProposal(repoPath);
|
|
30592
31419
|
}
|
|
30593
31420
|
async function handleAddStage(argv) {
|
|
30594
|
-
const repoPath =
|
|
30595
|
-
|
|
30596
|
-
|
|
31421
|
+
const repoPath = path40.resolve(argv.repo);
|
|
31422
|
+
const available = listStageTemplateIds();
|
|
31423
|
+
if (argv.list) {
|
|
31424
|
+
for (const id of available) {
|
|
31425
|
+
console.log(id);
|
|
31426
|
+
}
|
|
31427
|
+
return;
|
|
31428
|
+
}
|
|
31429
|
+
if (argv.custom) {
|
|
31430
|
+
if (!argv.template) {
|
|
31431
|
+
error(
|
|
31432
|
+
'Missing stage id. Usage: har env add-stage <id> --custom (--command "npm test" | --script) [--kind test] [--verification]'
|
|
31433
|
+
);
|
|
31434
|
+
process.exit(1);
|
|
31435
|
+
}
|
|
31436
|
+
header("har env add-stage --custom");
|
|
31437
|
+
info(`Repository: ${repoPath}`);
|
|
31438
|
+
info(`Stage: ${argv.template}`);
|
|
31439
|
+
try {
|
|
31440
|
+
const result = addCustomStage(repoPath, {
|
|
31441
|
+
id: argv.template,
|
|
31442
|
+
kind: argv.kind,
|
|
31443
|
+
command: argv.command,
|
|
31444
|
+
script: argv.script,
|
|
31445
|
+
description: argv.description,
|
|
31446
|
+
verification: argv.verification,
|
|
31447
|
+
force: argv.force
|
|
31448
|
+
});
|
|
31449
|
+
divider();
|
|
31450
|
+
success(`Custom stage registered: ${result.stageId} (kind: ${result.kind}, ${result.mode})`);
|
|
31451
|
+
for (const file of result.filesWritten) {
|
|
31452
|
+
info(` + ${file}`);
|
|
31453
|
+
}
|
|
31454
|
+
console.error("");
|
|
31455
|
+
console.error(" Next steps:");
|
|
31456
|
+
for (const step of result.nextSteps) {
|
|
31457
|
+
console.error(` ${step}`);
|
|
31458
|
+
}
|
|
31459
|
+
console.error("");
|
|
31460
|
+
console.error(" Docs: .har/STAGES.md");
|
|
31461
|
+
console.error("");
|
|
31462
|
+
} catch (err) {
|
|
31463
|
+
error(err.message);
|
|
31464
|
+
process.exit(1);
|
|
31465
|
+
}
|
|
31466
|
+
return;
|
|
31467
|
+
}
|
|
31468
|
+
if (!argv.template || !available.includes(argv.template)) {
|
|
31469
|
+
error(
|
|
31470
|
+
`Unknown stage template: ${argv.template ?? "(missing)"}. Available: ${available.join(", ")}. For a project-specific stage, use: har env add-stage <id> --custom`
|
|
31471
|
+
);
|
|
30597
31472
|
process.exit(1);
|
|
30598
31473
|
}
|
|
30599
31474
|
header("har env add-stage");
|
|
30600
31475
|
info(`Repository: ${repoPath}`);
|
|
30601
31476
|
info(`Template: ${argv.template}`);
|
|
30602
31477
|
try {
|
|
30603
|
-
const result = addStageTemplate(repoPath,
|
|
31478
|
+
const result = addStageTemplate(repoPath, argv.template, {
|
|
30604
31479
|
force: argv.force,
|
|
30605
31480
|
skipCi: argv.skipCi
|
|
30606
31481
|
});
|
|
@@ -30612,7 +31487,7 @@ async function handleAddStage(argv) {
|
|
|
30612
31487
|
console.error(` ${step}`);
|
|
30613
31488
|
}
|
|
30614
31489
|
console.error("");
|
|
30615
|
-
console.error(
|
|
31490
|
+
console.error(` Docs: ${result.docsPath}`);
|
|
30616
31491
|
console.error("");
|
|
30617
31492
|
} catch (err) {
|
|
30618
31493
|
error(err.message);
|
|
@@ -30620,7 +31495,7 @@ async function handleAddStage(argv) {
|
|
|
30620
31495
|
}
|
|
30621
31496
|
}
|
|
30622
31497
|
async function handlePreflight(argv) {
|
|
30623
|
-
const repo =
|
|
31498
|
+
const repo = path40.resolve(argv.repo);
|
|
30624
31499
|
const agentId = validateAgentId(argv.id, repo);
|
|
30625
31500
|
const result = await preflightEnvironment({
|
|
30626
31501
|
repoPath: repo,
|
|
@@ -30638,7 +31513,7 @@ async function handlePreflight(argv) {
|
|
|
30638
31513
|
process.exit(result.code);
|
|
30639
31514
|
}
|
|
30640
31515
|
async function handleLaunch(argv) {
|
|
30641
|
-
const repo =
|
|
31516
|
+
const repo = path40.resolve(argv.repo);
|
|
30642
31517
|
const agentId = validateAgentId(argv.id, repo);
|
|
30643
31518
|
let confirmReplace = argv.replace;
|
|
30644
31519
|
let force = argv.force;
|
|
@@ -30650,8 +31525,8 @@ async function handleLaunch(argv) {
|
|
|
30650
31525
|
console.error(guard.reason ?? "");
|
|
30651
31526
|
const readline5 = await import("readline");
|
|
30652
31527
|
const rl = readline5.createInterface({ input: process.stdin, output: process.stderr });
|
|
30653
|
-
const answer = await new Promise((
|
|
30654
|
-
rl.question("Replace this slot? [y/N] ",
|
|
31528
|
+
const answer = await new Promise((resolve33) => {
|
|
31529
|
+
rl.question("Replace this slot? [y/N] ", resolve33);
|
|
30655
31530
|
});
|
|
30656
31531
|
rl.close();
|
|
30657
31532
|
if (!/^[Yy]$/.test(answer.trim())) {
|
|
@@ -30667,8 +31542,8 @@ async function handleLaunch(argv) {
|
|
|
30667
31542
|
warn("Worktree has uncommitted changes.");
|
|
30668
31543
|
const readline5 = await import("readline");
|
|
30669
31544
|
const rl = readline5.createInterface({ input: process.stdin, output: process.stderr });
|
|
30670
|
-
const answer = await new Promise((
|
|
30671
|
-
rl.question("Discard uncommitted changes? [y/N] ",
|
|
31545
|
+
const answer = await new Promise((resolve33) => {
|
|
31546
|
+
rl.question("Discard uncommitted changes? [y/N] ", resolve33);
|
|
30672
31547
|
});
|
|
30673
31548
|
rl.close();
|
|
30674
31549
|
if (!/^[Yy]$/.test(answer.trim())) {
|
|
@@ -30697,6 +31572,13 @@ async function handleLaunch(argv) {
|
|
|
30697
31572
|
process.exit(result.code || 2);
|
|
30698
31573
|
}
|
|
30699
31574
|
if (result.code === 0 && result.workDir) {
|
|
31575
|
+
if (result.stderr) {
|
|
31576
|
+
for (const line of result.stderr.split("\n").filter(Boolean)) {
|
|
31577
|
+
if (/telemetry|mission control|otel/i.test(line)) {
|
|
31578
|
+
info(line);
|
|
31579
|
+
}
|
|
31580
|
+
}
|
|
31581
|
+
}
|
|
30700
31582
|
divider();
|
|
30701
31583
|
success(`Session ready \u2014 make ALL file edits under: ${result.workDir}`);
|
|
30702
31584
|
if (result.branch) info(`Branch: ${result.branch}`);
|
|
@@ -30715,7 +31597,7 @@ async function handleRecover(argv) {
|
|
|
30715
31597
|
});
|
|
30716
31598
|
}
|
|
30717
31599
|
async function handleVerify(argv) {
|
|
30718
|
-
const repo =
|
|
31600
|
+
const repo = path40.resolve(argv.repo);
|
|
30719
31601
|
const agentId = validateAgentId(argv.id, repo);
|
|
30720
31602
|
const result = await runVerification({
|
|
30721
31603
|
repoPath: repo,
|
|
@@ -30727,7 +31609,7 @@ async function handleVerify(argv) {
|
|
|
30727
31609
|
process.exit(result.code);
|
|
30728
31610
|
}
|
|
30729
31611
|
async function handleTeardown(argv) {
|
|
30730
|
-
const repo =
|
|
31612
|
+
const repo = path40.resolve(argv.repo);
|
|
30731
31613
|
const agentId = validateAgentId(argv.id, repo);
|
|
30732
31614
|
const result = await teardownEnvironment({
|
|
30733
31615
|
repoPath: repo,
|
|
@@ -30738,7 +31620,7 @@ async function handleTeardown(argv) {
|
|
|
30738
31620
|
process.exit(result.code);
|
|
30739
31621
|
}
|
|
30740
31622
|
async function handleComplete(argv) {
|
|
30741
|
-
const repo =
|
|
31623
|
+
const repo = path40.resolve(argv.repo);
|
|
30742
31624
|
const agentId = validateAgentId(argv.id, repo);
|
|
30743
31625
|
const result = await completeEnvironment({
|
|
30744
31626
|
repoPath: repo,
|
|
@@ -30759,7 +31641,7 @@ async function handleComplete(argv) {
|
|
|
30759
31641
|
process.exit(result.code);
|
|
30760
31642
|
}
|
|
30761
31643
|
async function handleStatus(argv) {
|
|
30762
|
-
const repoPath =
|
|
31644
|
+
const repoPath = path40.resolve(argv.repo);
|
|
30763
31645
|
if (argv.json) {
|
|
30764
31646
|
const status = collectEnvironmentStatus(repoPath);
|
|
30765
31647
|
const output = EnvironmentStatusSchema.parse(status);
|
|
@@ -30772,7 +31654,7 @@ async function handleStatus(argv) {
|
|
|
30772
31654
|
});
|
|
30773
31655
|
}
|
|
30774
31656
|
async function handleRunsList(argv) {
|
|
30775
|
-
const repoPath =
|
|
31657
|
+
const repoPath = path40.resolve(argv.repo);
|
|
30776
31658
|
const runs = listRuns(repoPath, { stageId: argv.stage, limit: argv.limit });
|
|
30777
31659
|
if (argv.json) {
|
|
30778
31660
|
process.stdout.write(JSON.stringify({ runs }, null, 2) + "\n");
|
|
@@ -30789,7 +31671,7 @@ async function handleRunsList(argv) {
|
|
|
30789
31671
|
}
|
|
30790
31672
|
}
|
|
30791
31673
|
async function handleRunsGet(argv) {
|
|
30792
|
-
const run2 = getRun(
|
|
31674
|
+
const run2 = getRun(path40.resolve(argv.repo), argv.runId);
|
|
30793
31675
|
if (!run2) {
|
|
30794
31676
|
error(`Run not found: ${argv.runId}`);
|
|
30795
31677
|
process.exit(1);
|
|
@@ -30862,102 +31744,7 @@ function printNextSteps(auto2) {
|
|
|
30862
31744
|
}
|
|
30863
31745
|
|
|
30864
31746
|
// src/cli/commands/control.ts
|
|
30865
|
-
var
|
|
30866
|
-
|
|
30867
|
-
// src/core/control-lifecycle.ts
|
|
30868
|
-
var import_child_process8 = require("child_process");
|
|
30869
|
-
var path34 = __toESM(require("path"));
|
|
30870
|
-
|
|
30871
|
-
// src/core/control-image.ts
|
|
30872
|
-
var DEFAULT_CONTROL_IMAGE = "theosfactory/har-control";
|
|
30873
|
-
function getControlImageName() {
|
|
30874
|
-
return process.env.HAR_CONTROL_IMAGE ?? DEFAULT_CONTROL_IMAGE;
|
|
30875
|
-
}
|
|
30876
|
-
function getControlImageTag() {
|
|
30877
|
-
return process.env.HAR_CONTROL_IMAGE_TAG ?? getHarPackageVersion();
|
|
30878
|
-
}
|
|
30879
|
-
function getControlImageRef() {
|
|
30880
|
-
return `${getControlImageName()}:${getControlImageTag()}`;
|
|
30881
|
-
}
|
|
30882
|
-
function shouldBuildControlLocally() {
|
|
30883
|
-
return process.env.HAR_CONTROL_BUILD === "true";
|
|
30884
|
-
}
|
|
30885
|
-
|
|
30886
|
-
// src/core/control-lifecycle.ts
|
|
30887
|
-
function resolveControlDir() {
|
|
30888
|
-
return path34.resolve(__dirname, "..", "control");
|
|
30889
|
-
}
|
|
30890
|
-
function resolveControlComposeFiles(options) {
|
|
30891
|
-
const controlDir = resolveControlDir();
|
|
30892
|
-
const files = [path34.join(controlDir, "docker-compose.yml")];
|
|
30893
|
-
if (options?.build ?? shouldBuildControlLocally()) {
|
|
30894
|
-
files.push(path34.join(controlDir, "docker-compose.build.yml"));
|
|
30895
|
-
}
|
|
30896
|
-
return files;
|
|
30897
|
-
}
|
|
30898
|
-
function buildDockerComposeEnv() {
|
|
30899
|
-
return {
|
|
30900
|
-
...process.env,
|
|
30901
|
-
HAR_CONTROL_IMAGE: getControlImageName(),
|
|
30902
|
-
HAR_CONTROL_IMAGE_TAG: getControlImageTag()
|
|
30903
|
-
};
|
|
30904
|
-
}
|
|
30905
|
-
function runDockerCompose(args, options) {
|
|
30906
|
-
const controlDir = resolveControlDir();
|
|
30907
|
-
const composeFiles = resolveControlComposeFiles(options);
|
|
30908
|
-
const composeArgs = composeFiles.flatMap((file) => ["-f", file]);
|
|
30909
|
-
const result = (0, import_child_process8.spawnSync)("docker", ["compose", ...composeArgs, ...args], {
|
|
30910
|
-
cwd: controlDir,
|
|
30911
|
-
stdio: "inherit",
|
|
30912
|
-
env: buildDockerComposeEnv()
|
|
30913
|
-
});
|
|
30914
|
-
if (result.error) {
|
|
30915
|
-
throw result.error;
|
|
30916
|
-
}
|
|
30917
|
-
return result.status ?? 1;
|
|
30918
|
-
}
|
|
30919
|
-
async function startMissionControl(options) {
|
|
30920
|
-
const build = options.build ?? shouldBuildControlLocally();
|
|
30921
|
-
const imageRef = getControlImageRef();
|
|
30922
|
-
if (!build) {
|
|
30923
|
-
const pullCode = runDockerCompose(["pull"], { build: false });
|
|
30924
|
-
if (pullCode !== 0) {
|
|
30925
|
-
return { code: pullCode, apiUrl: getControlApiUrl(), imageRef };
|
|
30926
|
-
}
|
|
30927
|
-
}
|
|
30928
|
-
const detach = options.detach !== false;
|
|
30929
|
-
const upArgs = detach ? ["up", "-d"] : ["up"];
|
|
30930
|
-
if (build) {
|
|
30931
|
-
upArgs.push("--build");
|
|
30932
|
-
}
|
|
30933
|
-
const code = runDockerCompose(upArgs, { build });
|
|
30934
|
-
return { code, apiUrl: getControlApiUrl(), imageRef };
|
|
30935
|
-
}
|
|
30936
|
-
async function syncReposAfterControlStart(cwd) {
|
|
30937
|
-
if (!isControlEnabled()) {
|
|
30938
|
-
return { synced: 0, failed: 0, apiReady: false };
|
|
30939
|
-
}
|
|
30940
|
-
const apiUrl = getControlApiUrl();
|
|
30941
|
-
const apiReady = await waitForControlApi(apiUrl);
|
|
30942
|
-
if (!apiReady) {
|
|
30943
|
-
return { synced: 0, failed: 0, apiReady: false };
|
|
30944
|
-
}
|
|
30945
|
-
const { synced, failed } = await syncAllKnownReposWithControl({ apiUrl, cwd });
|
|
30946
|
-
return { synced, failed, apiReady: true };
|
|
30947
|
-
}
|
|
30948
|
-
async function startControlAndSync(options) {
|
|
30949
|
-
const { code, apiUrl, imageRef } = await startMissionControl({
|
|
30950
|
-
detach: options?.detach,
|
|
30951
|
-
build: options?.build
|
|
30952
|
-
});
|
|
30953
|
-
if (code !== 0) {
|
|
30954
|
-
return { code, apiUrl, imageRef, synced: 0, failed: 0, apiReady: false };
|
|
30955
|
-
}
|
|
30956
|
-
const { synced, failed, apiReady } = await syncReposAfterControlStart(options?.cwd ?? process.cwd());
|
|
30957
|
-
return { code, apiUrl, imageRef, synced, failed, apiReady };
|
|
30958
|
-
}
|
|
30959
|
-
|
|
30960
|
-
// src/cli/commands/control.ts
|
|
31747
|
+
var path41 = __toESM(require("path"));
|
|
30961
31748
|
var controlCommand = {
|
|
30962
31749
|
command: "control <subcommand>",
|
|
30963
31750
|
describe: "Mission Control dashboard (local)",
|
|
@@ -31060,7 +31847,7 @@ async function handleDown() {
|
|
|
31060
31847
|
process.exit(code);
|
|
31061
31848
|
}
|
|
31062
31849
|
async function handleRegister(argv) {
|
|
31063
|
-
const repoPath =
|
|
31850
|
+
const repoPath = path41.resolve(argv.repo);
|
|
31064
31851
|
header("har control register");
|
|
31065
31852
|
info(`Repository: ${repoPath}`);
|
|
31066
31853
|
try {
|
|
@@ -31080,7 +31867,7 @@ async function handleRegister(argv) {
|
|
|
31080
31867
|
}
|
|
31081
31868
|
}
|
|
31082
31869
|
async function handleSync(argv) {
|
|
31083
|
-
const repoPath =
|
|
31870
|
+
const repoPath = path41.resolve(argv.repo);
|
|
31084
31871
|
try {
|
|
31085
31872
|
await syncRepoWithControl({
|
|
31086
31873
|
repoPath,
|
|
@@ -31119,7 +31906,7 @@ async function handleWatch(argv) {
|
|
|
31119
31906
|
};
|
|
31120
31907
|
const tick = async () => {
|
|
31121
31908
|
if (argv.repo) {
|
|
31122
|
-
await syncOne(
|
|
31909
|
+
await syncOne(path41.resolve(argv.repo));
|
|
31123
31910
|
return;
|
|
31124
31911
|
}
|
|
31125
31912
|
try {
|
|
@@ -31150,12 +31937,12 @@ async function handleLogin(argv) {
|
|
|
31150
31937
|
}
|
|
31151
31938
|
|
|
31152
31939
|
// src/cli/commands/hooks.ts
|
|
31153
|
-
var
|
|
31940
|
+
var path44 = __toESM(require("path"));
|
|
31154
31941
|
|
|
31155
31942
|
// src/core/hooks.ts
|
|
31156
|
-
var
|
|
31157
|
-
var
|
|
31158
|
-
var
|
|
31943
|
+
var fs37 = __toESM(require("fs"));
|
|
31944
|
+
var os12 = __toESM(require("os"));
|
|
31945
|
+
var path42 = __toESM(require("path"));
|
|
31159
31946
|
var MARKER_START = "# >>> har commit gate (managed by `har hooks`) >>>";
|
|
31160
31947
|
var MARKER_END = "# <<< har commit gate <<<";
|
|
31161
31948
|
var PRE_COMMIT_BLOCK = [
|
|
@@ -31180,7 +31967,7 @@ function resolveCheckoutRoot(cwd) {
|
|
|
31180
31967
|
function resolveHooksDir(checkoutDir) {
|
|
31181
31968
|
const hooksPath = tryGit2(checkoutDir, "rev-parse --git-path hooks");
|
|
31182
31969
|
if (!hooksPath) throw new Error(`Not a git checkout: ${checkoutDir}`);
|
|
31183
|
-
return
|
|
31970
|
+
return path42.resolve(checkoutDir, hooksPath);
|
|
31184
31971
|
}
|
|
31185
31972
|
function getConfiguredHooksPath(checkoutDir) {
|
|
31186
31973
|
return tryGit2(checkoutDir, "config core.hooksPath") || void 0;
|
|
@@ -31188,9 +31975,9 @@ function getConfiguredHooksPath(checkoutDir) {
|
|
|
31188
31975
|
function defaultHarInvocation() {
|
|
31189
31976
|
const entry = process.argv[1];
|
|
31190
31977
|
if (entry && entry.endsWith(".js")) {
|
|
31191
|
-
return `"${process.execPath}" "${
|
|
31978
|
+
return `"${process.execPath}" "${path42.resolve(entry)}"`;
|
|
31192
31979
|
}
|
|
31193
|
-
if (entry) return `"${
|
|
31980
|
+
if (entry) return `"${path42.resolve(entry)}"`;
|
|
31194
31981
|
return "har";
|
|
31195
31982
|
}
|
|
31196
31983
|
function buildHookScript(harInvocation, subcommand, failOpenNotice) {
|
|
@@ -31211,40 +31998,40 @@ exit 0
|
|
|
31211
31998
|
`;
|
|
31212
31999
|
}
|
|
31213
32000
|
function upsertMarkedBlock(filePath, block) {
|
|
31214
|
-
if (!
|
|
31215
|
-
|
|
32001
|
+
if (!fs37.existsSync(filePath)) {
|
|
32002
|
+
fs37.writeFileSync(filePath, `#!/bin/sh
|
|
31216
32003
|
${block}
|
|
31217
32004
|
`, { mode: 493 });
|
|
31218
32005
|
return "created";
|
|
31219
32006
|
}
|
|
31220
|
-
const content =
|
|
32007
|
+
const content = fs37.readFileSync(filePath, "utf8");
|
|
31221
32008
|
if (content.includes(MARKER_START)) {
|
|
31222
32009
|
const pattern = new RegExp(
|
|
31223
32010
|
`${escapeRegExp(MARKER_START)}[\\s\\S]*?${escapeRegExp(MARKER_END)}`
|
|
31224
32011
|
);
|
|
31225
|
-
|
|
31226
|
-
|
|
32012
|
+
fs37.writeFileSync(filePath, content.replace(pattern, block));
|
|
32013
|
+
fs37.chmodSync(filePath, 493);
|
|
31227
32014
|
return "updated";
|
|
31228
32015
|
}
|
|
31229
32016
|
const suffix = content.endsWith("\n") ? "" : "\n";
|
|
31230
|
-
|
|
32017
|
+
fs37.writeFileSync(filePath, `${content}${suffix}
|
|
31231
32018
|
${block}
|
|
31232
32019
|
`);
|
|
31233
|
-
|
|
32020
|
+
fs37.chmodSync(filePath, 493);
|
|
31234
32021
|
return "appended";
|
|
31235
32022
|
}
|
|
31236
32023
|
function removeMarkedBlock(filePath) {
|
|
31237
|
-
if (!
|
|
31238
|
-
const content =
|
|
32024
|
+
if (!fs37.existsSync(filePath)) return false;
|
|
32025
|
+
const content = fs37.readFileSync(filePath, "utf8");
|
|
31239
32026
|
if (!content.includes(MARKER_START)) return false;
|
|
31240
32027
|
const pattern = new RegExp(
|
|
31241
32028
|
`\\n?${escapeRegExp(MARKER_START)}[\\s\\S]*?${escapeRegExp(MARKER_END)}\\n?`
|
|
31242
32029
|
);
|
|
31243
32030
|
const stripped = content.replace(pattern, "\n");
|
|
31244
32031
|
if (stripped.replace(/^#!\/bin\/sh\n?/, "").trim() === "") {
|
|
31245
|
-
|
|
32032
|
+
fs37.rmSync(filePath);
|
|
31246
32033
|
} else {
|
|
31247
|
-
|
|
32034
|
+
fs37.writeFileSync(filePath, stripped);
|
|
31248
32035
|
}
|
|
31249
32036
|
return true;
|
|
31250
32037
|
}
|
|
@@ -31266,20 +32053,20 @@ Or re-run with --force to write into that directory anyway.`
|
|
|
31266
32053
|
);
|
|
31267
32054
|
}
|
|
31268
32055
|
const hooksDir = resolveHooksDir(checkout);
|
|
31269
|
-
|
|
32056
|
+
fs37.mkdirSync(hooksDir, { recursive: true });
|
|
31270
32057
|
const invocation = options.harInvocation ?? defaultHarInvocation();
|
|
31271
|
-
|
|
31272
|
-
|
|
32058
|
+
fs37.writeFileSync(
|
|
32059
|
+
path42.join(hooksDir, "har-pre-commit"),
|
|
31273
32060
|
buildHookScript(invocation, "check", "har: binary not found; skipping commit gate (reinstall with har hooks install)"),
|
|
31274
32061
|
{ mode: 493 }
|
|
31275
32062
|
);
|
|
31276
|
-
|
|
31277
|
-
|
|
32063
|
+
fs37.writeFileSync(
|
|
32064
|
+
path42.join(hooksDir, "har-post-commit"),
|
|
31278
32065
|
buildHookScript(invocation, "record-commit", "har: binary not found; skipping commit association"),
|
|
31279
32066
|
{ mode: 493 }
|
|
31280
32067
|
);
|
|
31281
|
-
const preCommit = upsertMarkedBlock(
|
|
31282
|
-
const postCommit = upsertMarkedBlock(
|
|
32068
|
+
const preCommit = upsertMarkedBlock(path42.join(hooksDir, "pre-commit"), PRE_COMMIT_BLOCK);
|
|
32069
|
+
const postCommit = upsertMarkedBlock(path42.join(hooksDir, "post-commit"), POST_COMMIT_BLOCK);
|
|
31283
32070
|
ensureValidationsIgnored(checkout);
|
|
31284
32071
|
return { hooksDir, preCommit, postCommit };
|
|
31285
32072
|
}
|
|
@@ -31288,12 +32075,12 @@ function uninstallHooks(repoPath) {
|
|
|
31288
32075
|
if (!checkout) throw new Error(`Not a git repository: ${repoPath}`);
|
|
31289
32076
|
const hooksDir = resolveHooksDir(checkout);
|
|
31290
32077
|
let removed = false;
|
|
31291
|
-
removed = removeMarkedBlock(
|
|
31292
|
-
removed = removeMarkedBlock(
|
|
32078
|
+
removed = removeMarkedBlock(path42.join(hooksDir, "pre-commit")) || removed;
|
|
32079
|
+
removed = removeMarkedBlock(path42.join(hooksDir, "post-commit")) || removed;
|
|
31293
32080
|
for (const file of ["har-pre-commit", "har-post-commit"]) {
|
|
31294
|
-
const full =
|
|
31295
|
-
if (
|
|
31296
|
-
|
|
32081
|
+
const full = path42.join(hooksDir, file);
|
|
32082
|
+
if (fs37.existsSync(full)) {
|
|
32083
|
+
fs37.rmSync(full);
|
|
31297
32084
|
removed = true;
|
|
31298
32085
|
}
|
|
31299
32086
|
}
|
|
@@ -31310,9 +32097,9 @@ function getCommitGateConfig(checkoutDir) {
|
|
|
31310
32097
|
function isAgentWorktree(checkoutDir) {
|
|
31311
32098
|
const branch = getCurrentBranch(checkoutDir);
|
|
31312
32099
|
if (branch && /^har-agent-\d+$/.test(branch)) return true;
|
|
31313
|
-
const worktreesRoot =
|
|
31314
|
-
const resolved =
|
|
31315
|
-
return resolved.startsWith(`${worktreesRoot}${
|
|
32100
|
+
const worktreesRoot = path42.join(os12.homedir(), "worktrees");
|
|
32101
|
+
const resolved = path42.resolve(checkoutDir);
|
|
32102
|
+
return resolved.startsWith(`${worktreesRoot}${path42.sep}`) && /-agent-\d+$/.test(path42.basename(resolved));
|
|
31316
32103
|
}
|
|
31317
32104
|
function resolveEffectiveMode(checkoutDir, gate) {
|
|
31318
32105
|
if (!gate.enabled) return "off";
|
|
@@ -31325,15 +32112,15 @@ function getHooksStatus(repoPath) {
|
|
|
31325
32112
|
const hooksDir = resolveHooksDir(checkout);
|
|
31326
32113
|
const gate = getCommitGateConfig(checkout);
|
|
31327
32114
|
const hasBlock = (name) => {
|
|
31328
|
-
const file =
|
|
31329
|
-
return
|
|
32115
|
+
const file = path42.join(hooksDir, name);
|
|
32116
|
+
return fs37.existsSync(file) && fs37.readFileSync(file, "utf8").includes(MARKER_START);
|
|
31330
32117
|
};
|
|
31331
32118
|
return {
|
|
31332
32119
|
checkout,
|
|
31333
32120
|
hooksDir,
|
|
31334
32121
|
configuredHooksPath: getConfiguredHooksPath(checkout),
|
|
31335
|
-
preCommitInstalled: hasBlock("pre-commit") &&
|
|
31336
|
-
postCommitInstalled: hasBlock("post-commit") &&
|
|
32122
|
+
preCommitInstalled: hasBlock("pre-commit") && fs37.existsSync(path42.join(hooksDir, "har-pre-commit")),
|
|
32123
|
+
postCommitInstalled: hasBlock("post-commit") && fs37.existsSync(path42.join(hooksDir, "har-post-commit")),
|
|
31337
32124
|
gate,
|
|
31338
32125
|
effectiveMode: resolveEffectiveMode(checkout, gate)
|
|
31339
32126
|
};
|
|
@@ -31344,7 +32131,7 @@ function checkCommitGate(cwd) {
|
|
|
31344
32131
|
}
|
|
31345
32132
|
const checkout = resolveCheckoutRoot(cwd);
|
|
31346
32133
|
if (!checkout) return { exitCode: 0, messages: [] };
|
|
31347
|
-
if (!
|
|
32134
|
+
if (!fs37.existsSync(path42.join(checkout, ".har", "stages.json"))) {
|
|
31348
32135
|
return { exitCode: 0, messages: [] };
|
|
31349
32136
|
}
|
|
31350
32137
|
const gate = getCommitGateConfig(checkout);
|
|
@@ -31415,8 +32202,8 @@ async function recordCommitAssociation(cwd) {
|
|
|
31415
32202
|
}
|
|
31416
32203
|
|
|
31417
32204
|
// src/core/claude-hooks.ts
|
|
31418
|
-
var
|
|
31419
|
-
var
|
|
32205
|
+
var fs38 = __toESM(require("fs"));
|
|
32206
|
+
var path43 = __toESM(require("path"));
|
|
31420
32207
|
var CLAUDE_GUARD_RELATIVE_PATH = ".har/hooks/claude-worktree-guard.sh";
|
|
31421
32208
|
var CLAUDE_SETTINGS_RELATIVE_PATH = ".claude/settings.json";
|
|
31422
32209
|
var GUARD_MATCHER = "Edit|Write|MultiEdit|NotebookEdit";
|
|
@@ -31427,8 +32214,8 @@ function isHarGuardEntry(entry) {
|
|
|
31427
32214
|
);
|
|
31428
32215
|
}
|
|
31429
32216
|
function readSettings(settingsPath) {
|
|
31430
|
-
if (!
|
|
31431
|
-
const raw =
|
|
32217
|
+
if (!fs38.existsSync(settingsPath)) return {};
|
|
32218
|
+
const raw = fs38.readFileSync(settingsPath, "utf8").trim();
|
|
31432
32219
|
if (raw === "") return {};
|
|
31433
32220
|
const parsed = JSON.parse(raw);
|
|
31434
32221
|
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
@@ -31437,19 +32224,19 @@ function readSettings(settingsPath) {
|
|
|
31437
32224
|
return parsed;
|
|
31438
32225
|
}
|
|
31439
32226
|
function writeSettings(settingsPath, settings) {
|
|
31440
|
-
|
|
31441
|
-
|
|
32227
|
+
fs38.mkdirSync(path43.dirname(settingsPath), { recursive: true });
|
|
32228
|
+
fs38.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n");
|
|
31442
32229
|
}
|
|
31443
32230
|
function installClaudeGuard(repoPath) {
|
|
31444
32231
|
const templatePath = resolveTemplateFile("claude-worktree-guard.sh.template");
|
|
31445
32232
|
if (!templatePath) {
|
|
31446
32233
|
throw new Error("Guard template not found (claude-worktree-guard.sh.template). Run npm run build.");
|
|
31447
32234
|
}
|
|
31448
|
-
const guardScript =
|
|
31449
|
-
|
|
31450
|
-
|
|
31451
|
-
|
|
31452
|
-
const settingsPath =
|
|
32235
|
+
const guardScript = path43.join(repoPath, CLAUDE_GUARD_RELATIVE_PATH);
|
|
32236
|
+
fs38.mkdirSync(path43.dirname(guardScript), { recursive: true });
|
|
32237
|
+
fs38.copyFileSync(templatePath, guardScript);
|
|
32238
|
+
fs38.chmodSync(guardScript, 493);
|
|
32239
|
+
const settingsPath = path43.join(repoPath, CLAUDE_SETTINGS_RELATIVE_PATH);
|
|
31453
32240
|
const settings = readSettings(settingsPath);
|
|
31454
32241
|
const hooks = settings.hooks ?? {};
|
|
31455
32242
|
const preToolUse = (hooks.PreToolUse ?? []).filter((entry) => !isHarGuardEntry(entry));
|
|
@@ -31463,15 +32250,15 @@ function installClaudeGuard(repoPath) {
|
|
|
31463
32250
|
}
|
|
31464
32251
|
function uninstallClaudeGuard(repoPath) {
|
|
31465
32252
|
let removed = false;
|
|
31466
|
-
const guardScript =
|
|
31467
|
-
if (
|
|
31468
|
-
|
|
32253
|
+
const guardScript = path43.join(repoPath, CLAUDE_GUARD_RELATIVE_PATH);
|
|
32254
|
+
if (fs38.existsSync(guardScript)) {
|
|
32255
|
+
fs38.rmSync(guardScript);
|
|
31469
32256
|
removed = true;
|
|
31470
|
-
const parent =
|
|
31471
|
-
if (
|
|
32257
|
+
const parent = path43.dirname(guardScript);
|
|
32258
|
+
if (fs38.existsSync(parent) && fs38.readdirSync(parent).length === 0) fs38.rmdirSync(parent);
|
|
31472
32259
|
}
|
|
31473
|
-
const settingsPath =
|
|
31474
|
-
if (
|
|
32260
|
+
const settingsPath = path43.join(repoPath, CLAUDE_SETTINGS_RELATIVE_PATH);
|
|
32261
|
+
if (fs38.existsSync(settingsPath)) {
|
|
31475
32262
|
const settings = readSettings(settingsPath);
|
|
31476
32263
|
const preToolUse = settings.hooks?.PreToolUse;
|
|
31477
32264
|
if (preToolUse) {
|
|
@@ -31496,8 +32283,8 @@ function uninstallClaudeGuard(repoPath) {
|
|
|
31496
32283
|
return { removed };
|
|
31497
32284
|
}
|
|
31498
32285
|
function claudeGuardInstalled(repoPath) {
|
|
31499
|
-
const settingsPath =
|
|
31500
|
-
if (!
|
|
32286
|
+
const settingsPath = path43.join(repoPath, CLAUDE_SETTINGS_RELATIVE_PATH);
|
|
32287
|
+
if (!fs38.existsSync(settingsPath)) return false;
|
|
31501
32288
|
try {
|
|
31502
32289
|
const settings = readSettings(settingsPath);
|
|
31503
32290
|
return (settings.hooks?.PreToolUse ?? []).some(isHarGuardEntry);
|
|
@@ -31513,7 +32300,7 @@ function repoOption(y2) {
|
|
|
31513
32300
|
function handleInstall2(argv) {
|
|
31514
32301
|
try {
|
|
31515
32302
|
if (argv.claude) {
|
|
31516
|
-
const result2 = installClaudeGuard(
|
|
32303
|
+
const result2 = installClaudeGuard(path44.resolve(argv.repo));
|
|
31517
32304
|
success("Claude Code worktree guard installed.");
|
|
31518
32305
|
info(`Guard script: ${result2.guardScript}`);
|
|
31519
32306
|
info(`Settings: ${result2.settingsPath} (PreToolUse: Edit|Write|MultiEdit|NotebookEdit)`);
|
|
@@ -31521,7 +32308,7 @@ function handleInstall2(argv) {
|
|
|
31521
32308
|
info("Commit both files so the guard travels with the repo.");
|
|
31522
32309
|
return;
|
|
31523
32310
|
}
|
|
31524
|
-
const result = installHooks({ repoPath:
|
|
32311
|
+
const result = installHooks({ repoPath: path44.resolve(argv.repo), force: argv.force });
|
|
31525
32312
|
success(`Commit gate installed in ${result.hooksDir}`);
|
|
31526
32313
|
info(`pre-commit: ${result.preCommit}, post-commit: ${result.postCommit}`);
|
|
31527
32314
|
info("Commits of unverified change batches will be blocked in agent worktrees.");
|
|
@@ -31534,7 +32321,7 @@ function handleInstall2(argv) {
|
|
|
31534
32321
|
function handleUninstall(argv) {
|
|
31535
32322
|
try {
|
|
31536
32323
|
if (argv.claude) {
|
|
31537
|
-
const result2 = uninstallClaudeGuard(
|
|
32324
|
+
const result2 = uninstallClaudeGuard(path44.resolve(argv.repo));
|
|
31538
32325
|
if (result2.removed) {
|
|
31539
32326
|
success("Claude Code worktree guard removed.");
|
|
31540
32327
|
} else {
|
|
@@ -31542,7 +32329,7 @@ function handleUninstall(argv) {
|
|
|
31542
32329
|
}
|
|
31543
32330
|
return;
|
|
31544
32331
|
}
|
|
31545
|
-
const result = uninstallHooks(
|
|
32332
|
+
const result = uninstallHooks(path44.resolve(argv.repo));
|
|
31546
32333
|
if (result.removed) {
|
|
31547
32334
|
success(`Commit gate removed from ${result.hooksDir}`);
|
|
31548
32335
|
} else {
|
|
@@ -31555,7 +32342,7 @@ function handleUninstall(argv) {
|
|
|
31555
32342
|
}
|
|
31556
32343
|
function handleStatus2(argv) {
|
|
31557
32344
|
try {
|
|
31558
|
-
const status = getHooksStatus(
|
|
32345
|
+
const status = getHooksStatus(path44.resolve(argv.repo));
|
|
31559
32346
|
if (argv.json) {
|
|
31560
32347
|
process.stdout.write(`${JSON.stringify(status, null, 2)}
|
|
31561
32348
|
`);
|
|
@@ -31573,7 +32360,7 @@ function handleStatus2(argv) {
|
|
|
31573
32360
|
);
|
|
31574
32361
|
info(`Effective: ${status.effectiveMode} (in this checkout)`);
|
|
31575
32362
|
info(
|
|
31576
|
-
`Claude guard: ${claudeGuardInstalled(
|
|
32363
|
+
`Claude guard: ${claudeGuardInstalled(path44.resolve(argv.repo)) ? `installed (${CLAUDE_GUARD_RELATIVE_PATH})` : "not installed"}`
|
|
31577
32364
|
);
|
|
31578
32365
|
} catch (err) {
|
|
31579
32366
|
error(err instanceof Error ? err.message : String(err));
|
|
@@ -31834,10 +32621,10 @@ function assignProp(target, prop, value) {
|
|
|
31834
32621
|
configurable: true
|
|
31835
32622
|
});
|
|
31836
32623
|
}
|
|
31837
|
-
function getElementAtPath(obj,
|
|
31838
|
-
if (!
|
|
32624
|
+
function getElementAtPath(obj, path47) {
|
|
32625
|
+
if (!path47)
|
|
31839
32626
|
return obj;
|
|
31840
|
-
return
|
|
32627
|
+
return path47.reduce((acc, key) => acc?.[key], obj);
|
|
31841
32628
|
}
|
|
31842
32629
|
function promiseAllObject(promisesObj) {
|
|
31843
32630
|
const keys = Object.keys(promisesObj);
|
|
@@ -32157,11 +32944,11 @@ function aborted(x2, startIndex = 0) {
|
|
|
32157
32944
|
}
|
|
32158
32945
|
return false;
|
|
32159
32946
|
}
|
|
32160
|
-
function prefixIssues(
|
|
32947
|
+
function prefixIssues(path47, issues) {
|
|
32161
32948
|
return issues.map((iss) => {
|
|
32162
32949
|
var _a3;
|
|
32163
32950
|
(_a3 = iss).path ?? (_a3.path = []);
|
|
32164
|
-
iss.path.unshift(
|
|
32951
|
+
iss.path.unshift(path47);
|
|
32165
32952
|
return iss;
|
|
32166
32953
|
});
|
|
32167
32954
|
}
|
|
@@ -37484,7 +38271,7 @@ var Protocol = class {
|
|
|
37484
38271
|
return;
|
|
37485
38272
|
}
|
|
37486
38273
|
const pollInterval = task2.pollInterval ?? this._options?.defaultTaskPollInterval ?? 1e3;
|
|
37487
|
-
await new Promise((
|
|
38274
|
+
await new Promise((resolve33) => setTimeout(resolve33, pollInterval));
|
|
37488
38275
|
options?.signal?.throwIfAborted();
|
|
37489
38276
|
}
|
|
37490
38277
|
} catch (error3) {
|
|
@@ -37501,7 +38288,7 @@ var Protocol = class {
|
|
|
37501
38288
|
*/
|
|
37502
38289
|
request(request, resultSchema, options) {
|
|
37503
38290
|
const { relatedRequestId, resumptionToken, onresumptiontoken, task, relatedTask } = options ?? {};
|
|
37504
|
-
return new Promise((
|
|
38291
|
+
return new Promise((resolve33, reject) => {
|
|
37505
38292
|
const earlyReject = (error3) => {
|
|
37506
38293
|
reject(error3);
|
|
37507
38294
|
};
|
|
@@ -37579,7 +38366,7 @@ var Protocol = class {
|
|
|
37579
38366
|
if (!parseResult.success) {
|
|
37580
38367
|
reject(parseResult.error);
|
|
37581
38368
|
} else {
|
|
37582
|
-
|
|
38369
|
+
resolve33(parseResult.data);
|
|
37583
38370
|
}
|
|
37584
38371
|
} catch (error3) {
|
|
37585
38372
|
reject(error3);
|
|
@@ -37840,12 +38627,12 @@ var Protocol = class {
|
|
|
37840
38627
|
}
|
|
37841
38628
|
} catch {
|
|
37842
38629
|
}
|
|
37843
|
-
return new Promise((
|
|
38630
|
+
return new Promise((resolve33, reject) => {
|
|
37844
38631
|
if (signal.aborted) {
|
|
37845
38632
|
reject(new McpError(ErrorCode.InvalidRequest, "Request cancelled"));
|
|
37846
38633
|
return;
|
|
37847
38634
|
}
|
|
37848
|
-
const timeoutId = setTimeout(
|
|
38635
|
+
const timeoutId = setTimeout(resolve33, interval);
|
|
37849
38636
|
signal.addEventListener("abort", () => {
|
|
37850
38637
|
clearTimeout(timeoutId);
|
|
37851
38638
|
reject(new McpError(ErrorCode.InvalidRequest, "Request cancelled"));
|
|
@@ -38635,7 +39422,7 @@ var Server = class extends Protocol {
|
|
|
38635
39422
|
};
|
|
38636
39423
|
|
|
38637
39424
|
// src/mcp/server.ts
|
|
38638
|
-
var
|
|
39425
|
+
var path45 = __toESM(require("path"));
|
|
38639
39426
|
|
|
38640
39427
|
// node_modules/@modelcontextprotocol/sdk/dist/esm/server/stdio.js
|
|
38641
39428
|
var import_node_process2 = __toESM(require("node:process"), 1);
|
|
@@ -38718,12 +39505,12 @@ var StdioServerTransport = class {
|
|
|
38718
39505
|
this.onclose?.();
|
|
38719
39506
|
}
|
|
38720
39507
|
send(message) {
|
|
38721
|
-
return new Promise((
|
|
39508
|
+
return new Promise((resolve33) => {
|
|
38722
39509
|
const json = serializeMessage(message);
|
|
38723
39510
|
if (this._stdout.write(json)) {
|
|
38724
|
-
|
|
39511
|
+
resolve33();
|
|
38725
39512
|
} else {
|
|
38726
|
-
this._stdout.once("drain",
|
|
39513
|
+
this._stdout.once("drain", resolve33);
|
|
38727
39514
|
}
|
|
38728
39515
|
});
|
|
38729
39516
|
}
|
|
@@ -39267,7 +40054,7 @@ async function handleMcpToolCall(toolName, args, defaultRepo = ".") {
|
|
|
39267
40054
|
const input = ControlUpInputSchema.parse({ ...args, repo });
|
|
39268
40055
|
const result = await startControlAndSync({
|
|
39269
40056
|
detach: input.detach,
|
|
39270
|
-
cwd:
|
|
40057
|
+
cwd: path45.resolve(input.repo)
|
|
39271
40058
|
});
|
|
39272
40059
|
if (result.code !== 0) {
|
|
39273
40060
|
return {
|
|
@@ -39329,9 +40116,231 @@ var mcpCommand = {
|
|
|
39329
40116
|
}
|
|
39330
40117
|
};
|
|
39331
40118
|
|
|
40119
|
+
// src/cli/commands/telemetry.ts
|
|
40120
|
+
var fs39 = __toESM(require("fs"));
|
|
40121
|
+
var path46 = __toESM(require("path"));
|
|
40122
|
+
function printSignals() {
|
|
40123
|
+
info("Collected when enabled (local Mission Control only):");
|
|
40124
|
+
for (const signal of TELEMETRY_SIGNALS) {
|
|
40125
|
+
info(` \u2022 ${signal}`);
|
|
40126
|
+
}
|
|
40127
|
+
info("Disable anytime: har telemetry off");
|
|
40128
|
+
}
|
|
40129
|
+
async function handleStatus3(argv) {
|
|
40130
|
+
const preference = readTelemetryPreference();
|
|
40131
|
+
const enabled = isTelemetryEnabled();
|
|
40132
|
+
const apiUrl = getControlApiUrl();
|
|
40133
|
+
const reachable = enabled ? await isControlApiReachable(apiUrl) : false;
|
|
40134
|
+
const payload = {
|
|
40135
|
+
enabled,
|
|
40136
|
+
preferenceEnabled: preference.enabled,
|
|
40137
|
+
preferencePath: getTelemetryPreferencePath(),
|
|
40138
|
+
updatedAt: preference.updatedAt ?? null,
|
|
40139
|
+
envOverride: process.env.HAR_TELEMETRY ?? null,
|
|
40140
|
+
controlApiUrl: apiUrl,
|
|
40141
|
+
controlReachable: reachable,
|
|
40142
|
+
otelEndpoint: `${apiUrl.replace(/\/$/, "")}/api/otel`,
|
|
40143
|
+
signals: [...TELEMETRY_SIGNALS]
|
|
40144
|
+
};
|
|
40145
|
+
if (argv.json) {
|
|
40146
|
+
process.stdout.write(`${JSON.stringify(payload, null, 2)}
|
|
40147
|
+
`);
|
|
40148
|
+
return;
|
|
40149
|
+
}
|
|
40150
|
+
header("har telemetry status");
|
|
40151
|
+
info(`Enabled: ${enabled ? "yes" : "no"}${process.env.HAR_TELEMETRY ? " (HAR_TELEMETRY override)" : ""}`);
|
|
40152
|
+
info(`Preference: ${getTelemetryPreferencePath()}${preference.updatedAt ? ` (updated ${preference.updatedAt})` : " (default on)"}`);
|
|
40153
|
+
info(`Mission Control: ${apiUrl} (${reachable ? "reachable" : "not reachable"})`);
|
|
40154
|
+
info(`OTLP endpoint: ${payload.otelEndpoint}`);
|
|
40155
|
+
printSignals();
|
|
40156
|
+
}
|
|
40157
|
+
async function handleOn() {
|
|
40158
|
+
header("har telemetry on");
|
|
40159
|
+
writeTelemetryPreference(true);
|
|
40160
|
+
success("Telemetry enabled (opt-out). Agent usage will be stored in local Mission Control.");
|
|
40161
|
+
printSignals();
|
|
40162
|
+
const result = await ensureTelemetryInfrastructure({ startIfNeeded: true });
|
|
40163
|
+
if (result.message) success(result.message);
|
|
40164
|
+
if (result.warning) warn(result.warning);
|
|
40165
|
+
if (result.otelReady) {
|
|
40166
|
+
info(`OTLP ingest: ${result.apiUrl.replace(/\/$/, "")}/api/otel`);
|
|
40167
|
+
info("Usage appears under Mission Control \u2192 Worktrees. Disable: har telemetry off");
|
|
40168
|
+
}
|
|
40169
|
+
}
|
|
40170
|
+
async function handleOff() {
|
|
40171
|
+
header("har telemetry off");
|
|
40172
|
+
writeTelemetryPreference(false);
|
|
40173
|
+
success("Telemetry disabled.");
|
|
40174
|
+
info("Future launches will not inject OTEL exporters or auto-start Mission Control.");
|
|
40175
|
+
info("Existing usage rows in Mission Control are kept. Historical data is not deleted.");
|
|
40176
|
+
}
|
|
40177
|
+
async function handleWriteEnv(argv) {
|
|
40178
|
+
const repoPath = path46.resolve(argv.repo);
|
|
40179
|
+
const harnessRoot = resolveHarnessRoot(repoPath);
|
|
40180
|
+
const session = readSlotRegistry(harnessRoot, argv.agentId);
|
|
40181
|
+
const workDir = argv.workDir ?? session?.workDir ?? repoPath;
|
|
40182
|
+
const branch = argv.branch ?? session?.branch;
|
|
40183
|
+
const suffix = argv.suffix ?? session?.suffix;
|
|
40184
|
+
const purpose = argv.purpose ?? session?.purpose;
|
|
40185
|
+
const sessionKey = argv.sessionKey ?? buildSessionKey({
|
|
40186
|
+
branch,
|
|
40187
|
+
agentId: argv.agentId,
|
|
40188
|
+
suffix,
|
|
40189
|
+
createdAt: session?.createdAt
|
|
40190
|
+
});
|
|
40191
|
+
const envFile = argv.envFile ?? path46.join(workDir, `.env.agent.${argv.agentId}`);
|
|
40192
|
+
let otelReady = argv.otelReady;
|
|
40193
|
+
if (otelReady === void 0 && isTelemetryEnabled()) {
|
|
40194
|
+
const ensured = await ensureTelemetryInfrastructure({ startIfNeeded: false });
|
|
40195
|
+
otelReady = ensured.otelReady;
|
|
40196
|
+
}
|
|
40197
|
+
const attrs = {
|
|
40198
|
+
sessionKey,
|
|
40199
|
+
agentId: argv.agentId,
|
|
40200
|
+
repoPath,
|
|
40201
|
+
workDir,
|
|
40202
|
+
branch,
|
|
40203
|
+
suffix,
|
|
40204
|
+
purpose
|
|
40205
|
+
};
|
|
40206
|
+
appendTelemetryEnvToFile(envFile, attrs, { otelReady: otelReady !== false && isTelemetryEnabled() });
|
|
40207
|
+
success(`Wrote telemetry env to ${envFile}`);
|
|
40208
|
+
}
|
|
40209
|
+
function handlePrintEnv(argv) {
|
|
40210
|
+
const repoPath = path46.resolve(argv.repo);
|
|
40211
|
+
const workDir = argv.workDir ?? repoPath;
|
|
40212
|
+
const sessionKey = buildSessionKey({
|
|
40213
|
+
branch: argv.branch,
|
|
40214
|
+
agentId: argv.agentId,
|
|
40215
|
+
suffix: argv.suffix
|
|
40216
|
+
});
|
|
40217
|
+
process.stdout.write(
|
|
40218
|
+
buildTelemetryEnvBlock(
|
|
40219
|
+
{
|
|
40220
|
+
sessionKey,
|
|
40221
|
+
agentId: argv.agentId,
|
|
40222
|
+
repoPath,
|
|
40223
|
+
workDir,
|
|
40224
|
+
branch: argv.branch,
|
|
40225
|
+
suffix: argv.suffix
|
|
40226
|
+
},
|
|
40227
|
+
{ otelReady: argv.otelReady && isTelemetryEnabled() }
|
|
40228
|
+
)
|
|
40229
|
+
);
|
|
40230
|
+
}
|
|
40231
|
+
function handleCodexSnippet(argv) {
|
|
40232
|
+
const repoPath = path46.resolve(argv.repo);
|
|
40233
|
+
const harnessRoot = resolveHarnessRoot(repoPath);
|
|
40234
|
+
const session = readSlotRegistry(harnessRoot, argv.agentId);
|
|
40235
|
+
const workDir = argv.workDir ?? session?.workDir ?? repoPath;
|
|
40236
|
+
const branch = argv.branch ?? session?.branch;
|
|
40237
|
+
const suffix = argv.suffix ?? session?.suffix;
|
|
40238
|
+
const sessionKey = buildSessionKey({
|
|
40239
|
+
branch,
|
|
40240
|
+
agentId: argv.agentId,
|
|
40241
|
+
suffix,
|
|
40242
|
+
createdAt: session?.createdAt
|
|
40243
|
+
});
|
|
40244
|
+
const snippet = buildCodexOtelSnippet({
|
|
40245
|
+
sessionKey,
|
|
40246
|
+
agentId: argv.agentId,
|
|
40247
|
+
repoPath,
|
|
40248
|
+
workDir,
|
|
40249
|
+
branch,
|
|
40250
|
+
suffix,
|
|
40251
|
+
purpose: session?.purpose
|
|
40252
|
+
});
|
|
40253
|
+
if (argv.write) {
|
|
40254
|
+
const outDir = path46.join(harnessRoot, ".har", "telemetry");
|
|
40255
|
+
fs39.mkdirSync(outDir, { recursive: true });
|
|
40256
|
+
const outPath = path46.join(outDir, "codex.otel.toml.snippet");
|
|
40257
|
+
fs39.writeFileSync(outPath, snippet);
|
|
40258
|
+
success(`Wrote ${outPath}`);
|
|
40259
|
+
info("Merge the [otel] table into ~/.codex/config.toml manually.");
|
|
40260
|
+
return;
|
|
40261
|
+
}
|
|
40262
|
+
process.stdout.write(snippet);
|
|
40263
|
+
}
|
|
40264
|
+
var telemetryCommand = {
|
|
40265
|
+
command: "telemetry <subcommand>",
|
|
40266
|
+
describe: "Agent usage telemetry (Claude Code / Codex \u2192 Mission Control)",
|
|
40267
|
+
builder: (yargs) => yargs.command(
|
|
40268
|
+
"status",
|
|
40269
|
+
"Show telemetry preference and Mission Control reachability",
|
|
40270
|
+
(y2) => y2.option("json", { type: "boolean", default: false }),
|
|
40271
|
+
(argv) => {
|
|
40272
|
+
void handleStatus3({ json: argv.json });
|
|
40273
|
+
}
|
|
40274
|
+
).command(
|
|
40275
|
+
"on",
|
|
40276
|
+
"Enable telemetry (default) and ensure Mission Control is running",
|
|
40277
|
+
() => {
|
|
40278
|
+
},
|
|
40279
|
+
() => {
|
|
40280
|
+
void handleOn();
|
|
40281
|
+
}
|
|
40282
|
+
).command(
|
|
40283
|
+
"off",
|
|
40284
|
+
"Disable telemetry (no OTEL injection, no MC auto-start)",
|
|
40285
|
+
() => {
|
|
40286
|
+
},
|
|
40287
|
+
() => {
|
|
40288
|
+
void handleOff();
|
|
40289
|
+
}
|
|
40290
|
+
).command(
|
|
40291
|
+
"write-env",
|
|
40292
|
+
"Append session + OTEL env vars to .env.agent.<id>",
|
|
40293
|
+
(y2) => y2.option("agent-id", { type: "number", demandOption: true }).option("repo", { type: "string", default: "." }).option("env-file", { type: "string" }).option("work-dir", { type: "string" }).option("branch", { type: "string" }).option("suffix", { type: "string" }).option("purpose", { type: "string" }).option("session-key", { type: "string" }).option("otel-ready", { type: "boolean" }),
|
|
40294
|
+
(argv) => handleWriteEnv({
|
|
40295
|
+
agentId: argv["agent-id"],
|
|
40296
|
+
repo: argv.repo,
|
|
40297
|
+
envFile: argv["env-file"],
|
|
40298
|
+
workDir: argv["work-dir"],
|
|
40299
|
+
branch: argv.branch,
|
|
40300
|
+
suffix: argv.suffix,
|
|
40301
|
+
purpose: argv.purpose,
|
|
40302
|
+
sessionKey: argv["session-key"],
|
|
40303
|
+
otelReady: argv["otel-ready"]
|
|
40304
|
+
}).catch((err) => {
|
|
40305
|
+
error(err instanceof Error ? err.message : String(err));
|
|
40306
|
+
process.exitCode = 1;
|
|
40307
|
+
})
|
|
40308
|
+
).command(
|
|
40309
|
+
"print-env",
|
|
40310
|
+
"Print telemetry env block to stdout",
|
|
40311
|
+
(y2) => y2.option("agent-id", { type: "number", demandOption: true }).option("repo", { type: "string", default: "." }).option("work-dir", { type: "string" }).option("branch", { type: "string" }).option("suffix", { type: "string" }).option("otel-ready", { type: "boolean", default: true }),
|
|
40312
|
+
(argv) => handlePrintEnv({
|
|
40313
|
+
agentId: argv["agent-id"],
|
|
40314
|
+
repo: argv.repo,
|
|
40315
|
+
workDir: argv["work-dir"],
|
|
40316
|
+
branch: argv.branch,
|
|
40317
|
+
suffix: argv.suffix,
|
|
40318
|
+
otelReady: argv["otel-ready"]
|
|
40319
|
+
})
|
|
40320
|
+
).command(
|
|
40321
|
+
"codex-snippet",
|
|
40322
|
+
"Print or write a Codex [otel] config snippet",
|
|
40323
|
+
(y2) => y2.option("agent-id", { type: "number", demandOption: true }).option("repo", { type: "string", default: "." }).option("work-dir", { type: "string" }).option("branch", { type: "string" }).option("suffix", { type: "string" }).option("write", {
|
|
40324
|
+
type: "boolean",
|
|
40325
|
+
default: false,
|
|
40326
|
+
describe: "Write to .har/telemetry/codex.otel.toml.snippet"
|
|
40327
|
+
}),
|
|
40328
|
+
(argv) => handleCodexSnippet({
|
|
40329
|
+
agentId: argv["agent-id"],
|
|
40330
|
+
repo: argv.repo,
|
|
40331
|
+
workDir: argv["work-dir"],
|
|
40332
|
+
branch: argv.branch,
|
|
40333
|
+
suffix: argv.suffix,
|
|
40334
|
+
write: argv.write
|
|
40335
|
+
})
|
|
40336
|
+
).demandCommand(1, "Please specify a subcommand: status, on, off, write-env, print-env, codex-snippet"),
|
|
40337
|
+
handler: () => {
|
|
40338
|
+
}
|
|
40339
|
+
};
|
|
40340
|
+
|
|
39332
40341
|
// src/cli/index.ts
|
|
39333
40342
|
async function runCli() {
|
|
39334
|
-
await yargs_default(hideBin(process.argv)).scriptName("har").usage("$0 <command> [options]").command(envCommand).command(agentsCommand).command(controlCommand).command(hooksCommand).command(mcpCommand).demandCommand(1, "Please specify a command. Try: har env init").strict().help().version(getHarPackageVersion()).parse();
|
|
40343
|
+
await yargs_default(hideBin(process.argv)).scriptName("har").usage("$0 <command> [options]").command(envCommand).command(agentsCommand).command(controlCommand).command(hooksCommand).command(mcpCommand).command(telemetryCommand).demandCommand(1, "Please specify a command. Try: har env init").strict().help().version(getHarPackageVersion()).parse();
|
|
39335
40344
|
}
|
|
39336
40345
|
|
|
39337
40346
|
// src/index.ts
|