@osfactory/har 0.49.2 → 0.50.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +84 -47
- package/dist/templates/adaptation-prompt-init.md +5 -0
- package/dist/templates/adaptation-prompt-maintain.md +1 -1
- package/dist/templates/har-boilerplate/agent-cli.sh +7 -6
- package/dist/templates/har-boilerplate/agent-slot.sh +1 -1
- package/dist/templates/har-boilerplate/harness.env +117 -0
- package/dist/templates/har-boilerplate/launch.sh +3 -3
- package/dist/templates/har-boilerplate/provision-toolchain.sh +136 -15
- package/dist/templates/har-boilerplate/setup-infra.sh +1 -1
- package/dist/templates/har-boilerplate/teardown.sh +1 -1
- package/dist/templates/har-boilerplate-cli/agent-slot.sh +1 -1
- package/dist/templates/har-boilerplate-cli/harness.env +117 -0
- package/dist/templates/har-boilerplate-cli/launch.sh +3 -2
- package/dist/templates/har-boilerplate-cli/provision-toolchain.sh +136 -15
- package/dist/templates/har-boilerplate-cli/setup-infra.sh +1 -1
- package/dist/templates/har-boilerplate-ios/harness.env +117 -0
- package/dist/templates/har-boilerplate-ios/provision-toolchain.sh +136 -15
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -29790,10 +29790,32 @@ var init_slot_launch_guard_occupied = __esm({
|
|
|
29790
29790
|
}
|
|
29791
29791
|
});
|
|
29792
29792
|
|
|
29793
|
+
// src/utils/package-runner.ts
|
|
29794
|
+
function commandExists(command2) {
|
|
29795
|
+
const result = (0, import_child_process6.spawnSync)("sh", ["-c", `command -v ${JSON.stringify(command2)}`], {
|
|
29796
|
+
encoding: "utf8"
|
|
29797
|
+
});
|
|
29798
|
+
return result.status === 0 && Boolean(result.stdout?.trim());
|
|
29799
|
+
}
|
|
29800
|
+
function packageRunner() {
|
|
29801
|
+
if (cached !== void 0) return cached;
|
|
29802
|
+
const available = RUNNERS.find((runner) => commandExists(runner.split(" ")[0]));
|
|
29803
|
+
cached = available ?? RUNNERS[0];
|
|
29804
|
+
return cached;
|
|
29805
|
+
}
|
|
29806
|
+
var import_child_process6, RUNNERS, cached;
|
|
29807
|
+
var init_package_runner = __esm({
|
|
29808
|
+
"src/utils/package-runner.ts"() {
|
|
29809
|
+
"use strict";
|
|
29810
|
+
import_child_process6 = require("child_process");
|
|
29811
|
+
RUNNERS = ["npx --yes", "bunx", "pnpm dlx", "yarn dlx"];
|
|
29812
|
+
}
|
|
29813
|
+
});
|
|
29814
|
+
|
|
29793
29815
|
// src/core/slot-preflight.ts
|
|
29794
29816
|
function listPm2Processes() {
|
|
29795
29817
|
try {
|
|
29796
|
-
const raw = (0,
|
|
29818
|
+
const raw = (0, import_child_process7.execSync)(`${packageRunner()} pm2 jlist`, {
|
|
29797
29819
|
encoding: "utf8",
|
|
29798
29820
|
stdio: ["pipe", "pipe", "ignore"],
|
|
29799
29821
|
timeout: 3e3
|
|
@@ -29874,7 +29896,7 @@ function inspectSlotReadiness(repoPath, agentId, options = {}) {
|
|
|
29874
29896
|
remediation: "Stop the other harness session (./.har/teardown.sh in that repo) or use a different agent slot.",
|
|
29875
29897
|
details: { processes: foreign.processes }
|
|
29876
29898
|
});
|
|
29877
|
-
remediations.push(
|
|
29899
|
+
remediations.push(`Inspect with: ${packageRunner()} pm2 jlist | grep agent-${agentId}`);
|
|
29878
29900
|
}
|
|
29879
29901
|
if (usesPm2 && pm2Procs) {
|
|
29880
29902
|
const slotPrefix = `har-${projectName}-agent-${agentId}-`;
|
|
@@ -30008,7 +30030,7 @@ function formatPreflightReport(agentId, readiness) {
|
|
|
30008
30030
|
}
|
|
30009
30031
|
return lines2.join("\n");
|
|
30010
30032
|
}
|
|
30011
|
-
var path16,
|
|
30033
|
+
var path16, import_child_process7;
|
|
30012
30034
|
var init_slot_preflight = __esm({
|
|
30013
30035
|
"src/core/slot-preflight.ts"() {
|
|
30014
30036
|
"use strict";
|
|
@@ -30019,14 +30041,15 @@ var init_slot_preflight = __esm({
|
|
|
30019
30041
|
init_slot_launch_guard_occupied();
|
|
30020
30042
|
init_slot_ports();
|
|
30021
30043
|
init_slot_registry();
|
|
30022
|
-
|
|
30044
|
+
import_child_process7 = require("child_process");
|
|
30045
|
+
init_package_runner();
|
|
30023
30046
|
}
|
|
30024
30047
|
});
|
|
30025
30048
|
|
|
30026
30049
|
// src/core/slot-status.ts
|
|
30027
30050
|
function readGitRemote(repoPath) {
|
|
30028
30051
|
try {
|
|
30029
|
-
const remote = (0,
|
|
30052
|
+
const remote = (0, import_child_process8.execSync)("git remote get-url origin", {
|
|
30030
30053
|
cwd: repoPath,
|
|
30031
30054
|
encoding: "utf8",
|
|
30032
30055
|
stdio: ["pipe", "pipe", "ignore"]
|
|
@@ -30038,7 +30061,7 @@ function readGitRemote(repoPath) {
|
|
|
30038
30061
|
}
|
|
30039
30062
|
function runGit2(cwd, args) {
|
|
30040
30063
|
try {
|
|
30041
|
-
return (0,
|
|
30064
|
+
return (0, import_child_process8.execSync)(`git ${args}`, {
|
|
30042
30065
|
cwd,
|
|
30043
30066
|
encoding: "utf8",
|
|
30044
30067
|
stdio: ["pipe", "pipe", "ignore"]
|
|
@@ -30114,7 +30137,7 @@ function lastBuildPass(run2) {
|
|
|
30114
30137
|
}
|
|
30115
30138
|
function listPm2Processes2() {
|
|
30116
30139
|
try {
|
|
30117
|
-
const raw = (0,
|
|
30140
|
+
const raw = (0, import_child_process8.execSync)(`${packageRunner()} pm2 jlist`, {
|
|
30118
30141
|
encoding: "utf8",
|
|
30119
30142
|
stdio: ["pipe", "pipe", "ignore"],
|
|
30120
30143
|
timeout: 3e3
|
|
@@ -30217,14 +30240,14 @@ function collectEnvironmentStatus(repoPath) {
|
|
|
30217
30240
|
generatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
30218
30241
|
};
|
|
30219
30242
|
}
|
|
30220
|
-
var fs16, os6, path17,
|
|
30243
|
+
var fs16, os6, path17, import_child_process8, BYPASS_WARNING_MS;
|
|
30221
30244
|
var init_slot_status = __esm({
|
|
30222
30245
|
"src/core/slot-status.ts"() {
|
|
30223
30246
|
"use strict";
|
|
30224
30247
|
fs16 = __toESM(require("fs"));
|
|
30225
30248
|
os6 = __toESM(require("os"));
|
|
30226
30249
|
path17 = __toESM(require("path"));
|
|
30227
|
-
|
|
30250
|
+
import_child_process8 = require("child_process");
|
|
30228
30251
|
init_env();
|
|
30229
30252
|
init_manifest();
|
|
30230
30253
|
init_stages();
|
|
@@ -30232,6 +30255,7 @@ var init_slot_status = __esm({
|
|
|
30232
30255
|
init_runs();
|
|
30233
30256
|
init_slot_registry();
|
|
30234
30257
|
init_slot_preflight();
|
|
30258
|
+
init_package_runner();
|
|
30235
30259
|
BYPASS_WARNING_MS = 15 * 60 * 1e3;
|
|
30236
30260
|
}
|
|
30237
30261
|
});
|
|
@@ -32336,7 +32360,8 @@ async function syncRepoWithPortal(options, controlApiUrl) {
|
|
|
32336
32360
|
);
|
|
32337
32361
|
const { selected: newRuns } = selectSince(runs, runsSince, runTimestamp);
|
|
32338
32362
|
let repoId;
|
|
32339
|
-
|
|
32363
|
+
const deltaRepoId = () => stored?.repoId ?? repoId;
|
|
32364
|
+
await syncRunBatches(newRuns, repoPath, runsTarget, deltaRepoId, dryRun, async (batch, i) => {
|
|
32340
32365
|
const body = i === 0 ? { ...syncBody, runs: batch } : { path: repoPath, runs: batch };
|
|
32341
32366
|
const res = await postPortal(portal, "/api/sync", body, dryRun);
|
|
32342
32367
|
if (i === 0 && typeof res?.repositoryId === "string") repoId = res.repositoryId;
|
|
@@ -53490,8 +53515,8 @@ exec "${otelHookCommand}" run \\
|
|
|
53490
53515
|
}
|
|
53491
53516
|
return wrapperPath;
|
|
53492
53517
|
}
|
|
53493
|
-
function
|
|
53494
|
-
const result = (0,
|
|
53518
|
+
function commandExists3(command2) {
|
|
53519
|
+
const result = (0, import_child_process11.spawnSync)("sh", ["-c", `command -v ${JSON.stringify(command2)}`], {
|
|
53495
53520
|
encoding: "utf8"
|
|
53496
53521
|
});
|
|
53497
53522
|
return result.status === 0 && Boolean(result.stdout?.trim());
|
|
@@ -53501,25 +53526,37 @@ function resolveOtelHookCommand(hooksHome) {
|
|
|
53501
53526
|
if (fs41.existsSync(local)) return local;
|
|
53502
53527
|
return null;
|
|
53503
53528
|
}
|
|
53529
|
+
function resolveHooksInstaller(hooksHome) {
|
|
53530
|
+
const candidates = [
|
|
53531
|
+
{
|
|
53532
|
+
command: "npm",
|
|
53533
|
+
args: ["install", "--prefix", hooksHome, "--no-fund", "--no-audit", OTEL_HOOKS_PACKAGE]
|
|
53534
|
+
},
|
|
53535
|
+
{ command: "bun", args: ["add", "--cwd", hooksHome, OTEL_HOOKS_PACKAGE] },
|
|
53536
|
+
{ command: "pnpm", args: ["add", "--dir", hooksHome, OTEL_HOOKS_PACKAGE] }
|
|
53537
|
+
];
|
|
53538
|
+
return candidates.find((candidate) => commandExists3(candidate.command)) ?? null;
|
|
53539
|
+
}
|
|
53504
53540
|
function tryInstallPackage(hooksHome) {
|
|
53505
53541
|
fs41.mkdirSync(hooksHome, { recursive: true });
|
|
53506
|
-
|
|
53542
|
+
const installer = resolveHooksInstaller(hooksHome);
|
|
53543
|
+
if (!installer) {
|
|
53507
53544
|
return {
|
|
53508
53545
|
ok: false,
|
|
53509
|
-
detail: "
|
|
53546
|
+
detail: "no package manager found; install Node.js or bun to use @osfactory/otel-hook"
|
|
53510
53547
|
};
|
|
53511
53548
|
}
|
|
53512
|
-
const
|
|
53513
|
-
|
|
53514
|
-
|
|
53515
|
-
|
|
53516
|
-
);
|
|
53549
|
+
const described = `${installer.command} ${installer.args.join(" ")}`;
|
|
53550
|
+
const result = (0, import_child_process11.spawnSync)(installer.command, installer.args, {
|
|
53551
|
+
encoding: "utf8",
|
|
53552
|
+
timeout: 18e4
|
|
53553
|
+
});
|
|
53517
53554
|
if (result.status === 0 && fs41.existsSync(localOtelHookBin(hooksHome))) {
|
|
53518
|
-
return { ok: true, detail:
|
|
53555
|
+
return { ok: true, detail: described };
|
|
53519
53556
|
}
|
|
53520
53557
|
return {
|
|
53521
53558
|
ok: false,
|
|
53522
|
-
detail:
|
|
53559
|
+
detail: `${described} failed: ${(result.stderr || result.stdout || "").trim().slice(0, 400)}`
|
|
53523
53560
|
};
|
|
53524
53561
|
}
|
|
53525
53562
|
function uninstallLegacyPythonHooks(hooksHome) {
|
|
@@ -53530,9 +53567,9 @@ function uninstallLegacyPythonHooks(hooksHome) {
|
|
|
53530
53567
|
["pip3", ["uninstall", "-y", "opentelemetry-hooks"]]
|
|
53531
53568
|
];
|
|
53532
53569
|
for (const [cmd, args] of attempts) {
|
|
53533
|
-
if (!
|
|
53570
|
+
if (!commandExists3(cmd)) continue;
|
|
53534
53571
|
try {
|
|
53535
|
-
(0,
|
|
53572
|
+
(0, import_child_process11.spawnSync)(cmd, args, { encoding: "utf8", timeout: 6e4 });
|
|
53536
53573
|
} catch {
|
|
53537
53574
|
}
|
|
53538
53575
|
}
|
|
@@ -53564,7 +53601,7 @@ function normalizeLegacyProviderFlagsInCommand(command2) {
|
|
|
53564
53601
|
}
|
|
53565
53602
|
function runSetupProvider(providerId, otelHookCommand, wrapperPath) {
|
|
53566
53603
|
const hookCommand = `${wrapperPath} --provider ${providerId}`;
|
|
53567
|
-
const result = (0,
|
|
53604
|
+
const result = (0, import_child_process11.spawnSync)(
|
|
53568
53605
|
otelHookCommand,
|
|
53569
53606
|
[
|
|
53570
53607
|
"setup",
|
|
@@ -53805,11 +53842,11 @@ function ensureOtelHooks(options) {
|
|
|
53805
53842
|
function disableOtelHooksExport() {
|
|
53806
53843
|
return writeOtelHooksConfig(buildOtelHooksConfig({ enabled: false }));
|
|
53807
53844
|
}
|
|
53808
|
-
var
|
|
53845
|
+
var import_child_process11, fs41, os15, path47, OTEL_HOOKS_PACKAGE, OTEL_HOOKS_PROVIDERS, OTEL_HOOKS_AGENTS, LEGACY_CURSOR_ONLY_HOOK_EVENTS;
|
|
53809
53846
|
var init_otel_hooks = __esm({
|
|
53810
53847
|
"src/core/otel-hooks.ts"() {
|
|
53811
53848
|
"use strict";
|
|
53812
|
-
|
|
53849
|
+
import_child_process11 = require("child_process");
|
|
53813
53850
|
fs41 = __toESM(require("fs"));
|
|
53814
53851
|
os15 = __toESM(require("os"));
|
|
53815
53852
|
path47 = __toESM(require("path"));
|
|
@@ -70178,13 +70215,13 @@ var path44 = __toESM(require("path"));
|
|
|
70178
70215
|
var readline5 = __toESM(require("readline"));
|
|
70179
70216
|
|
|
70180
70217
|
// src/utils/clipboard.ts
|
|
70181
|
-
var
|
|
70182
|
-
function
|
|
70183
|
-
const probe = process.platform === "win32" ? (0,
|
|
70218
|
+
var import_child_process9 = require("child_process");
|
|
70219
|
+
function commandExists2(command2) {
|
|
70220
|
+
const probe = process.platform === "win32" ? (0, import_child_process9.spawnSync)("where", [command2], { encoding: "utf8", stdio: ["ignore", "ignore", "ignore"] }) : (0, import_child_process9.spawnSync)("which", [command2], { encoding: "utf8", stdio: ["ignore", "ignore", "ignore"] });
|
|
70184
70221
|
return probe.status === 0;
|
|
70185
70222
|
}
|
|
70186
70223
|
function tryPipeCommand(command2, args, text) {
|
|
70187
|
-
const result = (0,
|
|
70224
|
+
const result = (0, import_child_process9.spawnSync)(command2, args, {
|
|
70188
70225
|
input: text,
|
|
70189
70226
|
encoding: "utf8",
|
|
70190
70227
|
stdio: ["pipe", "ignore", "ignore"]
|
|
@@ -70199,12 +70236,12 @@ function writeOsc52Clipboard(text, stream = process.stderr) {
|
|
|
70199
70236
|
return true;
|
|
70200
70237
|
}
|
|
70201
70238
|
function copyViaPlatformTools(text) {
|
|
70202
|
-
if (process.platform === "darwin" &&
|
|
70239
|
+
if (process.platform === "darwin" && commandExists2("pbcopy")) {
|
|
70203
70240
|
if (tryPipeCommand("pbcopy", [], text)) return { ok: true, method: "pbcopy" };
|
|
70204
70241
|
}
|
|
70205
70242
|
if (process.platform === "win32") {
|
|
70206
|
-
if (
|
|
70207
|
-
const result = (0,
|
|
70243
|
+
if (commandExists2("powershell")) {
|
|
70244
|
+
const result = (0, import_child_process9.spawnSync)(
|
|
70208
70245
|
"powershell",
|
|
70209
70246
|
[
|
|
70210
70247
|
"-NoProfile",
|
|
@@ -70220,18 +70257,18 @@ function copyViaPlatformTools(text) {
|
|
|
70220
70257
|
);
|
|
70221
70258
|
if (result.status === 0 && !result.error) return { ok: true, method: "powershell" };
|
|
70222
70259
|
}
|
|
70223
|
-
if (
|
|
70260
|
+
if (commandExists2("clip") && tryPipeCommand("clip", [], text)) {
|
|
70224
70261
|
return { ok: true, method: "clip" };
|
|
70225
70262
|
}
|
|
70226
70263
|
}
|
|
70227
70264
|
if (process.platform === "linux" || process.platform === "freebsd" || process.platform === "openbsd") {
|
|
70228
|
-
if (
|
|
70265
|
+
if (commandExists2("wl-copy") && tryPipeCommand("wl-copy", [], text)) {
|
|
70229
70266
|
return { ok: true, method: "wl-copy" };
|
|
70230
70267
|
}
|
|
70231
|
-
if (
|
|
70268
|
+
if (commandExists2("xclip") && tryPipeCommand("xclip", ["-selection", "clipboard"], text)) {
|
|
70232
70269
|
return { ok: true, method: "xclip" };
|
|
70233
70270
|
}
|
|
70234
|
-
if (
|
|
70271
|
+
if (commandExists2("xsel") && tryPipeCommand("xsel", ["--clipboard", "--input"], text)) {
|
|
70235
70272
|
return { ok: true, method: "xsel" };
|
|
70236
70273
|
}
|
|
70237
70274
|
}
|
|
@@ -70450,7 +70487,7 @@ init_validation();
|
|
|
70450
70487
|
init_control_config();
|
|
70451
70488
|
|
|
70452
70489
|
// src/core/control-lifecycle.ts
|
|
70453
|
-
var
|
|
70490
|
+
var import_child_process10 = require("child_process");
|
|
70454
70491
|
var path46 = __toESM(require("path"));
|
|
70455
70492
|
|
|
70456
70493
|
// src/core/control-image.ts
|
|
@@ -70503,7 +70540,7 @@ function buildDockerRunArgs(options) {
|
|
|
70503
70540
|
return args;
|
|
70504
70541
|
}
|
|
70505
70542
|
function runDocker(args) {
|
|
70506
|
-
const result = (0,
|
|
70543
|
+
const result = (0, import_child_process10.spawnSync)("docker", args, {
|
|
70507
70544
|
stdio: "inherit",
|
|
70508
70545
|
env: process.env
|
|
70509
70546
|
});
|
|
@@ -70513,7 +70550,7 @@ function runDocker(args) {
|
|
|
70513
70550
|
return result.status ?? 1;
|
|
70514
70551
|
}
|
|
70515
70552
|
function controlContainerExists(name = CONTROL_CONTAINER_NAME) {
|
|
70516
|
-
const result = (0,
|
|
70553
|
+
const result = (0, import_child_process10.spawnSync)("docker", ["ps", "-aq", "-f", `name=^${name}$`], {
|
|
70517
70554
|
encoding: "utf8",
|
|
70518
70555
|
stdio: ["pipe", "pipe", "ignore"]
|
|
70519
70556
|
});
|
|
@@ -72353,13 +72390,13 @@ init_portal_credentials();
|
|
|
72353
72390
|
// src/core/portal-login.ts
|
|
72354
72391
|
var http = __toESM(require("http"));
|
|
72355
72392
|
var import_crypto = require("crypto");
|
|
72356
|
-
var
|
|
72393
|
+
var import_child_process12 = require("child_process");
|
|
72357
72394
|
var CALLBACK_PATH = "/callback";
|
|
72358
72395
|
var LOGIN_TIMEOUT_MS = 3 * 6e4;
|
|
72359
72396
|
function openBrowser(url) {
|
|
72360
72397
|
const [cmd, args] = process.platform === "darwin" ? ["open", [url]] : process.platform === "win32" ? ["cmd", ["/c", "start", "", url]] : ["xdg-open", [url]];
|
|
72361
72398
|
try {
|
|
72362
|
-
const child = (0,
|
|
72399
|
+
const child = (0, import_child_process12.spawn)(cmd, args, { stdio: "ignore", detached: true });
|
|
72363
72400
|
child.on("error", () => {
|
|
72364
72401
|
});
|
|
72365
72402
|
child.unref();
|
|
@@ -73519,7 +73556,7 @@ __export(util_exports, {
|
|
|
73519
73556
|
assertNever: () => assertNever,
|
|
73520
73557
|
assertNotEqual: () => assertNotEqual,
|
|
73521
73558
|
assignProp: () => assignProp,
|
|
73522
|
-
cached: () =>
|
|
73559
|
+
cached: () => cached2,
|
|
73523
73560
|
captureStackTrace: () => captureStackTrace,
|
|
73524
73561
|
cleanEnum: () => cleanEnum,
|
|
73525
73562
|
cleanRegex: () => cleanRegex,
|
|
@@ -73584,7 +73621,7 @@ function jsonStringifyReplacer(_2, value) {
|
|
|
73584
73621
|
return value.toString();
|
|
73585
73622
|
return value;
|
|
73586
73623
|
}
|
|
73587
|
-
function
|
|
73624
|
+
function cached2(getter) {
|
|
73588
73625
|
const set = false;
|
|
73589
73626
|
return {
|
|
73590
73627
|
get value() {
|
|
@@ -73673,7 +73710,7 @@ var captureStackTrace = Error.captureStackTrace ? Error.captureStackTrace : (...
|
|
|
73673
73710
|
function isObject(data) {
|
|
73674
73711
|
return typeof data === "object" && data !== null && !Array.isArray(data);
|
|
73675
73712
|
}
|
|
73676
|
-
var allowsEval =
|
|
73713
|
+
var allowsEval = cached2(() => {
|
|
73677
73714
|
if (typeof navigator !== "undefined" && navigator?.userAgent?.includes("Cloudflare")) {
|
|
73678
73715
|
return false;
|
|
73679
73716
|
}
|
|
@@ -75168,7 +75205,7 @@ function handleOptionalObjectResult(result, final, key, input) {
|
|
|
75168
75205
|
}
|
|
75169
75206
|
var $ZodObject = /* @__PURE__ */ $constructor("$ZodObject", (inst, def) => {
|
|
75170
75207
|
$ZodType.init(inst, def);
|
|
75171
|
-
const _normalized =
|
|
75208
|
+
const _normalized = cached2(() => {
|
|
75172
75209
|
const keys = Object.keys(def.shape);
|
|
75173
75210
|
for (const k of keys) {
|
|
75174
75211
|
if (!(def.shape[k] instanceof $ZodType)) {
|
|
@@ -75403,7 +75440,7 @@ var $ZodDiscriminatedUnion = /* @__PURE__ */ $constructor("$ZodDiscriminatedUnio
|
|
|
75403
75440
|
}
|
|
75404
75441
|
return propValues;
|
|
75405
75442
|
});
|
|
75406
|
-
const disc =
|
|
75443
|
+
const disc = cached2(() => {
|
|
75407
75444
|
const opts = def.options;
|
|
75408
75445
|
const map = /* @__PURE__ */ new Map();
|
|
75409
75446
|
for (const o of opts) {
|
|
@@ -69,6 +69,11 @@ how this project is built and tested. Use toolchain variables from `.env.agent.<
|
|
|
69
69
|
The stock verify section is keyed by `HARNESS_ECOSYSTEM`; it is a starting point,
|
|
70
70
|
not the repo's final contract. Replace conventions that do not match this project.
|
|
71
71
|
|
|
72
|
+
`${NPM_BIN}` may resolve to bun, pnpm, or yarn as well as npm, so keep Node steps
|
|
73
|
+
package-manager agnostic: always `${NPM_BIN:-npm} run <script>` (never bare
|
|
74
|
+
`${NPM_BIN} test`, which runs bun's own test runner), and never npm-only flags
|
|
75
|
+
such as `--prefix` — use a subshell like `(cd docs && ${NPM_BIN:-npm} run build)`.
|
|
76
|
+
|
|
72
77
|
**Tier contract:**
|
|
73
78
|
|
|
74
79
|
| Mode | Command | Intent |
|
|
@@ -25,7 +25,7 @@ Prefer targeted edits over full rewrites. Key files to review:
|
|
|
25
25
|
Keep this accurate — it is the harness index. Update whenever scripts, stages, or workflow change.
|
|
26
26
|
|
|
27
27
|
### `.har/harness.env`, `verify.sh`, `provision-toolchain.sh`, `ecosystem.agent.template.cjs`, `CLAUDE.agent.md`
|
|
28
|
-
Align commands and instructions with the current stack. Verify steps must use toolchain paths from `.env.agent.<id>` (`PYTHON_BIN`, `NPM_BIN`, `XCODEBUILD_BIN`, …) — never hardcoded venv or interpreter paths. Replace stock ecosystem conventions that do not match the repository; do not leave npm/pytest/go/cargo/maven/gradle examples in place by accident.
|
|
28
|
+
Align commands and instructions with the current stack. Verify steps must use toolchain paths from `.env.agent.<id>` (`PYTHON_BIN`, `NPM_BIN`, `XCODEBUILD_BIN`, …) — never hardcoded venv or interpreter paths. `NPM_BIN` may be bun, pnpm, or yarn, so Node steps must use `${NPM_BIN:-npm} run <script>` and avoid npm-only flags such as `--prefix`. Replace stock ecosystem conventions that do not match the repository; do not leave npm/pytest/go/cargo/maven/gradle examples in place by accident.
|
|
29
29
|
|
|
30
30
|
### `.har/env.template`, `setup-infra.sh`, `docker-compose.agent.yml`
|
|
31
31
|
Update only if infra changed.
|
|
@@ -28,7 +28,7 @@ case "$COMMAND" in
|
|
|
28
28
|
ENV_FILE="$(resolve_agent_env_file "$AGENT_ID" "$REPO_ROOT" || true)"
|
|
29
29
|
WORKTREE_DIR="$(existing_slot_worktree "$AGENT_ID")"
|
|
30
30
|
REG_FILE="$(slot_registry_file "$AGENT_ID")"
|
|
31
|
-
PM2_RAW=$(
|
|
31
|
+
PM2_RAW=$($(har_pkg_exec) pm2 jlist 2>/dev/null || true)
|
|
32
32
|
PM2_FOUND=false
|
|
33
33
|
PM2_FOREIGN=false
|
|
34
34
|
PM2_LEGACY=false
|
|
@@ -123,22 +123,23 @@ process.stdin.on('end', () => {
|
|
|
123
123
|
logs)
|
|
124
124
|
SERVICE="${3:-}"
|
|
125
125
|
if [ -n "$SERVICE" ]; then
|
|
126
|
-
|
|
126
|
+
$(har_pkg_exec) pm2 logs "${PM2_SLOT_PREFIX}-${SERVICE}" --lines 100
|
|
127
127
|
else
|
|
128
|
-
|
|
128
|
+
$(har_pkg_exec) pm2 logs --name "${PM2_SLOT_PREFIX}" --lines 100
|
|
129
129
|
fi
|
|
130
130
|
;;
|
|
131
131
|
|
|
132
132
|
restart)
|
|
133
133
|
SERVICE="${3:-}"
|
|
134
|
+
PKG_EXEC="$(har_pkg_exec)"
|
|
134
135
|
if [ -n "$SERVICE" ]; then
|
|
135
|
-
|
|
136
|
+
$(har_pkg_exec) pm2 restart "${PM2_SLOT_PREFIX}-${SERVICE}"
|
|
136
137
|
else
|
|
137
|
-
|
|
138
|
+
$(har_pkg_exec) pm2 jlist 2>/dev/null | node -e "
|
|
138
139
|
const prefix = '${PM2_SLOT_PREFIX}-';
|
|
139
140
|
const d = JSON.parse(require('fs').readFileSync('/dev/stdin','utf8'));
|
|
140
141
|
const names = d.filter(x => x.name && x.name.startsWith(prefix)).map(x => x.name);
|
|
141
|
-
names.forEach(n => require('child_process').execSync('
|
|
142
|
+
names.forEach(n => require('child_process').execSync('${PKG_EXEC} pm2 restart ' + n, {stdio:'inherit'}));
|
|
142
143
|
if (names.length === 0) console.log('No processes found for ${PM2_SLOT_PREFIX}');
|
|
143
144
|
" 2>/dev/null || true
|
|
144
145
|
fi
|
|
@@ -184,7 +184,7 @@ har_check_foreign_pm2() {
|
|
|
184
184
|
local agent_id="$1"
|
|
185
185
|
local pm2_raw
|
|
186
186
|
har_harness_uses_pm2 || return 0
|
|
187
|
-
pm2_raw="$(
|
|
187
|
+
pm2_raw="$($(har_pkg_exec) pm2 jlist 2>/dev/null || true)"
|
|
188
188
|
[ -n "$pm2_raw" ] || return 0
|
|
189
189
|
set +e
|
|
190
190
|
echo "$pm2_raw" | node -e "
|
|
@@ -51,6 +51,10 @@ export HARNESS_AGENT_SLOT_MAX=5
|
|
|
51
51
|
# Ecosystem: auto (detect from manifests) | node | python | go | rust | java | ruby | none
|
|
52
52
|
export HARNESS_ECOSYSTEM="auto"
|
|
53
53
|
# export HARNESS_INSTALL_CMD="make deps"
|
|
54
|
+
# Node package manager: auto-detected from package.json "packageManager" and the
|
|
55
|
+
# lockfile (bun.lock → bun, pnpm-lock.yaml → pnpm, yarn.lock → yarn, else npm),
|
|
56
|
+
# falling back to whichever is installed. Set to pin one: npm | bun | pnpm | yarn
|
|
57
|
+
# export HARNESS_NODE_PACKAGE_MANAGER="bun"
|
|
54
58
|
|
|
55
59
|
# Shared infrastructure — space-separated docker compose service names from
|
|
56
60
|
# docker-compose.agent.yml. Started ONCE by setup-infra.sh and shared by all
|
|
@@ -66,6 +70,119 @@ har_infra_enabled() {
|
|
|
66
70
|
esac
|
|
67
71
|
}
|
|
68
72
|
|
|
73
|
+
# ── Node package manager helpers ──────────────────────────────────────────────
|
|
74
|
+
# Mirrored verbatim in harness.env and provision-toolchain.sh so a sourcing
|
|
75
|
+
# script and the provisioning subprocess resolve the same tool.
|
|
76
|
+
|
|
77
|
+
# Package managers HAR can drive, in fallback preference order.
|
|
78
|
+
HAR_NODE_PACKAGE_MANAGERS="npm bun pnpm yarn"
|
|
79
|
+
|
|
80
|
+
# Package manager the repo declares: an explicit HARNESS_NODE_PACKAGE_MANAGER
|
|
81
|
+
# wins, then package.json "packageManager", then the lockfile. Empty when the
|
|
82
|
+
# repo says nothing.
|
|
83
|
+
har_node_declared_package_manager() {
|
|
84
|
+
local dir="${1:-.}"
|
|
85
|
+
|
|
86
|
+
if [ -n "${HARNESS_NODE_PACKAGE_MANAGER:-}" ]; then
|
|
87
|
+
echo "$HARNESS_NODE_PACKAGE_MANAGER"
|
|
88
|
+
return
|
|
89
|
+
fi
|
|
90
|
+
if [ -f "$dir/package.json" ]; then
|
|
91
|
+
local field
|
|
92
|
+
field="$(sed -n 's/.*"packageManager"[[:space:]]*:[[:space:]]*"\([a-z]*\)@.*/\1/p' "$dir/package.json" | head -1)"
|
|
93
|
+
if [ -n "$field" ]; then
|
|
94
|
+
echo "$field"
|
|
95
|
+
return
|
|
96
|
+
fi
|
|
97
|
+
fi
|
|
98
|
+
if [ -f "$dir/bun.lock" ] || [ -f "$dir/bun.lockb" ]; then echo bun; return; fi
|
|
99
|
+
if [ -f "$dir/pnpm-lock.yaml" ]; then echo pnpm; return; fi
|
|
100
|
+
if [ -f "$dir/yarn.lock" ]; then echo yarn; return; fi
|
|
101
|
+
if [ -f "$dir/package-lock.json" ]; then echo npm; return; fi
|
|
102
|
+
echo ""
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
# Package manager to actually run. A manager the repo declares but this machine
|
|
106
|
+
# lacks falls back to one that is installed, so a repo pinned to npm still
|
|
107
|
+
# provisions on a bun-only machine (and the reverse).
|
|
108
|
+
har_node_package_manager() {
|
|
109
|
+
local dir="${1:-.}"
|
|
110
|
+
local declared
|
|
111
|
+
declared="$(har_node_declared_package_manager "$dir")"
|
|
112
|
+
|
|
113
|
+
if [ -n "$declared" ] && command -v "$declared" >/dev/null 2>&1; then
|
|
114
|
+
echo "$declared"
|
|
115
|
+
return
|
|
116
|
+
fi
|
|
117
|
+
|
|
118
|
+
local candidate
|
|
119
|
+
for candidate in $HAR_NODE_PACKAGE_MANAGERS; do
|
|
120
|
+
if command -v "$candidate" >/dev/null 2>&1; then
|
|
121
|
+
echo "$candidate"
|
|
122
|
+
return
|
|
123
|
+
fi
|
|
124
|
+
done
|
|
125
|
+
|
|
126
|
+
echo "${declared:-npm}"
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
# Lockfile a manager writes, so a substitute install can clean up after itself.
|
|
130
|
+
har_node_lockfile() {
|
|
131
|
+
case "$1" in
|
|
132
|
+
bun) echo "bun.lock" ;;
|
|
133
|
+
npm) echo "package-lock.json" ;;
|
|
134
|
+
pnpm) echo "pnpm-lock.yaml" ;;
|
|
135
|
+
yarn) echo "yarn.lock" ;;
|
|
136
|
+
*) echo "" ;;
|
|
137
|
+
esac
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
# Install dependencies in a directory. When a substitute manager stands in for
|
|
141
|
+
# the one the repo declares, any lockfile it creates is removed afterwards:
|
|
142
|
+
# provisioning must not migrate the repo to a different package manager.
|
|
143
|
+
# (bun writes bun.lock even under --no-save, so the flags alone are not enough.)
|
|
144
|
+
har_node_install() {
|
|
145
|
+
local dir="$1"
|
|
146
|
+
local manager="$2"
|
|
147
|
+
local declared="${3:-}"
|
|
148
|
+
local substituting=false
|
|
149
|
+
local lockfile=""
|
|
150
|
+
local had_lockfile=false
|
|
151
|
+
|
|
152
|
+
if [ -n "$declared" ] && [ "$declared" != "$manager" ]; then
|
|
153
|
+
substituting=true
|
|
154
|
+
lockfile="$(har_node_lockfile "$manager")"
|
|
155
|
+
if [ -n "$lockfile" ] && [ -e "$dir/$lockfile" ]; then
|
|
156
|
+
had_lockfile=true
|
|
157
|
+
fi
|
|
158
|
+
fi
|
|
159
|
+
|
|
160
|
+
local code=0
|
|
161
|
+
(cd "$dir" && "$manager" install --silent) || code=$?
|
|
162
|
+
|
|
163
|
+
if [ "$substituting" = true ] && [ "$had_lockfile" = false ] && [ -n "$lockfile" ]; then
|
|
164
|
+
rm -f "$dir/$lockfile"
|
|
165
|
+
[ "$manager" = "bun" ] && rm -f "$dir/bun.lockb"
|
|
166
|
+
fi
|
|
167
|
+
return $code
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
# Package runner (npx equivalent) for one-off CLIs such as pm2, including the
|
|
171
|
+
# flag that keeps it non-interactive. Takes a manager name, or none to resolve
|
|
172
|
+
# from the environment and PATH.
|
|
173
|
+
har_pkg_exec() {
|
|
174
|
+
case "${1:-${HARNESS_NODE_PACKAGE_MANAGER:-}}" in
|
|
175
|
+
bun) echo "bunx"; return ;;
|
|
176
|
+
pnpm) echo "pnpm dlx"; return ;;
|
|
177
|
+
yarn) echo "yarn dlx"; return ;;
|
|
178
|
+
npm) echo "npx --yes"; return ;;
|
|
179
|
+
esac
|
|
180
|
+
if command -v npx >/dev/null 2>&1; then echo "npx --yes"; return; fi
|
|
181
|
+
if command -v bunx >/dev/null 2>&1; then echo "bunx"; return; fi
|
|
182
|
+
if command -v pnpm >/dev/null 2>&1; then echo "pnpm dlx"; return; fi
|
|
183
|
+
echo "npx --yes"
|
|
184
|
+
}
|
|
185
|
+
|
|
69
186
|
# Postgres client — uses host tools when installed, otherwise docker exec into
|
|
70
187
|
# the shared db container. Usage: har_pg psql -d postgres -c "SELECT 1"
|
|
71
188
|
# har_pg createdb -T template_x agent_1
|
|
@@ -249,13 +249,13 @@ DEBUG_PORT="$DEBUG_PORT" \
|
|
|
249
249
|
|
|
250
250
|
PM2_REGEX="$(har_pm2_delete_regex "$AGENT_ID")"
|
|
251
251
|
# Stop existing processes for this agent (project-scoped — never touch other harnesses).
|
|
252
|
-
|
|
252
|
+
$(har_pkg_exec) pm2 delete "$PM2_REGEX" 2>/dev/null || true
|
|
253
253
|
|
|
254
254
|
# Start PM2 processes
|
|
255
255
|
log "Starting PM2 processes..."
|
|
256
256
|
cd "$WORK_DIR"
|
|
257
|
-
|
|
258
|
-
|
|
257
|
+
$(har_pkg_exec) pm2 start "$ECOSYSTEM_FILE"
|
|
258
|
+
$(har_pkg_exec) pm2 save --force >/dev/null 2>&1 || true
|
|
259
259
|
|
|
260
260
|
# Health check
|
|
261
261
|
if [ -n "${HARNESS_HEALTH_CHECK_PATH:-}" ]; then
|