@osfactory/har 0.17.0 → 0.18.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 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 path48 = url.path;
769
- if (path48.length === 0) {
768
+ const path50 = url.path;
769
+ if (path50.length === 0) {
770
770
  return;
771
771
  }
772
- if (url.scheme === "file" && path48.length === 1 && isNormalizedWindowsDriveLetter(path48[0])) {
772
+ if (url.scheme === "file" && path50.length === 1 && isNormalizedWindowsDriveLetter(path50[0])) {
773
773
  return;
774
774
  }
775
- path48.pop();
775
+ path50.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(resolve33, reject) {
2095
+ return new Body.Promise(function(resolve35, 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
- resolve33(Buffer.concat(accum, accumBytes));
2129
+ resolve35(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(resolve33, reject) {
2804
+ return new fetch3.Promise(function(resolve35, 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
- resolve33(fetch3(new Request3(locationURL, requestOpts)));
2937
+ resolve35(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
- resolve33(response);
2958
+ resolve35(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
- resolve33(response);
2968
+ resolve35(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
- resolve33(response);
2980
+ resolve35(response);
2981
2981
  });
2982
2982
  raw.on("end", function() {
2983
2983
  if (!response) {
2984
2984
  response = new Response3(body, response_options);
2985
- resolve33(response);
2985
+ resolve35(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
- resolve33(response);
2993
+ resolve35(response);
2994
2994
  return;
2995
2995
  }
2996
2996
  response = new Response3(body, response_options);
2997
- resolve33(response);
2997
+ resolve35(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(path48, { mtimeMs, size }, filenameOrOptions, options = {}) {
6354
+ function createFileFromPath(path50, { 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: path48, size, lastModified: mtimeMs });
6361
+ const file = new FileFromPath({ path: path50, size, lastModified: mtimeMs });
6362
6362
  if (!filename) {
6363
6363
  filename = file.name;
6364
6364
  }
@@ -6367,13 +6367,13 @@ function createFileFromPath(path48, { mtimeMs, size }, filenameOrOptions, option
6367
6367
  lastModified: file.lastModified
6368
6368
  });
6369
6369
  }
6370
- function fileFromPathSync(path48, filenameOrOptions, options = {}) {
6371
- const stats = (0, import_fs5.statSync)(path48);
6372
- return createFileFromPath(path48, stats, filenameOrOptions, options);
6370
+ function fileFromPathSync(path50, filenameOrOptions, options = {}) {
6371
+ const stats = (0, import_fs5.statSync)(path50);
6372
+ return createFileFromPath(path50, stats, filenameOrOptions, options);
6373
6373
  }
6374
- async function fileFromPath2(path48, filenameOrOptions, options) {
6375
- const stats = await import_fs5.promises.stat(path48);
6376
- return createFileFromPath(path48, stats, filenameOrOptions, options);
6374
+ async function fileFromPath2(path50, filenameOrOptions, options) {
6375
+ const stats = await import_fs5.promises.stat(path50);
6376
+ return createFileFromPath(path50, 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({
@@ -6433,6 +6433,412 @@ var init_fileFromPath = __esm({
6433
6433
  }
6434
6434
  });
6435
6435
 
6436
+ // src/core/control-config.ts
6437
+ function getControlApiUrl() {
6438
+ return process.env.HAR_CONTROL_API_URL ?? DEFAULT_CONTROL_API_URL;
6439
+ }
6440
+ function isControlEnabled() {
6441
+ return process.env.HAR_CONTROL_DISABLED !== "true";
6442
+ }
6443
+ var DEFAULT_CONTROL_API_URL;
6444
+ var init_control_config = __esm({
6445
+ "src/core/control-config.ts"() {
6446
+ "use strict";
6447
+ DEFAULT_CONTROL_API_URL = "http://localhost:3847";
6448
+ }
6449
+ });
6450
+
6451
+ // src/core/telemetry-config.ts
6452
+ function getPreferencePath() {
6453
+ if (process.env.HAR_TELEMETRY_CONFIG_PATH) {
6454
+ return path35.resolve(process.env.HAR_TELEMETRY_CONFIG_PATH);
6455
+ }
6456
+ return path35.join(os9.homedir(), ".har", "telemetry.json");
6457
+ }
6458
+ function parseEnvOverride(raw) {
6459
+ if (raw === void 0 || raw === "") return void 0;
6460
+ const normalized = raw.trim().toLowerCase();
6461
+ if (["0", "false", "off", "no", "disabled"].includes(normalized)) return false;
6462
+ if (["1", "true", "on", "yes", "enabled"].includes(normalized)) return true;
6463
+ return void 0;
6464
+ }
6465
+ function normalizeSignals(enabled, raw) {
6466
+ if (!enabled) return { ...DEFAULT_SIGNALS_OFF };
6467
+ return {
6468
+ metrics: raw?.metrics !== false,
6469
+ logs: raw?.logs !== false,
6470
+ prompts: raw?.prompts === true,
6471
+ traces: raw?.traces !== false
6472
+ };
6473
+ }
6474
+ function readTelemetryPreference() {
6475
+ const preferencePath = getPreferencePath();
6476
+ try {
6477
+ if (!fs35.existsSync(preferencePath)) {
6478
+ return { enabled: true, signals: { ...DEFAULT_SIGNALS_ON } };
6479
+ }
6480
+ const parsed = JSON.parse(fs35.readFileSync(preferencePath, "utf8"));
6481
+ const enabled = parsed.enabled !== false;
6482
+ return {
6483
+ enabled,
6484
+ signals: normalizeSignals(enabled, parsed.signals),
6485
+ updatedAt: typeof parsed.updatedAt === "string" ? parsed.updatedAt : void 0
6486
+ };
6487
+ } catch {
6488
+ return { enabled: true, signals: { ...DEFAULT_SIGNALS_ON } };
6489
+ }
6490
+ }
6491
+ function writeTelemetryPreference(enabled, signals) {
6492
+ const current = readTelemetryPreference();
6493
+ const nextSignals = normalizeSignals(enabled, {
6494
+ ...current.signals,
6495
+ ...signals
6496
+ });
6497
+ const preference = {
6498
+ enabled,
6499
+ signals: enabled ? nextSignals : { ...DEFAULT_SIGNALS_OFF },
6500
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
6501
+ };
6502
+ const preferencePath = getPreferencePath();
6503
+ fs35.mkdirSync(path35.dirname(preferencePath), { recursive: true });
6504
+ fs35.writeFileSync(preferencePath, JSON.stringify(preference, null, 2) + "\n");
6505
+ return preference;
6506
+ }
6507
+ function isTelemetryEnabled() {
6508
+ const envOverride = parseEnvOverride(process.env.HAR_TELEMETRY);
6509
+ if (envOverride !== void 0) return envOverride;
6510
+ return readTelemetryPreference().enabled;
6511
+ }
6512
+ function getTelemetrySignals() {
6513
+ if (!isTelemetryEnabled()) return { ...DEFAULT_SIGNALS_OFF };
6514
+ return readTelemetryPreference().signals;
6515
+ }
6516
+ function getTelemetryPreferencePath() {
6517
+ return getPreferencePath();
6518
+ }
6519
+ var fs35, os9, path35, DEFAULT_SIGNALS_ON, DEFAULT_SIGNALS_OFF, TELEMETRY_SIGNALS;
6520
+ var init_telemetry_config = __esm({
6521
+ "src/core/telemetry-config.ts"() {
6522
+ "use strict";
6523
+ fs35 = __toESM(require("fs"));
6524
+ os9 = __toESM(require("os"));
6525
+ path35 = __toESM(require("path"));
6526
+ DEFAULT_SIGNALS_ON = {
6527
+ metrics: true,
6528
+ logs: true,
6529
+ prompts: false,
6530
+ /** Hooks are traces-first; usage metrics are derived from span gen_ai.usage.* in Mission Control. */
6531
+ traces: true
6532
+ };
6533
+ DEFAULT_SIGNALS_OFF = {
6534
+ metrics: false,
6535
+ logs: false,
6536
+ prompts: false,
6537
+ traces: false
6538
+ };
6539
+ TELEMETRY_SIGNALS = [
6540
+ "Traces (default): Cursor / Claude / Codex activity via opentelemetry-hooks \u2192 Mission Control",
6541
+ "Logs/events (default): hook lifecycle logs without prompt bodies",
6542
+ "Metrics (default): token usage derived from span gen_ai.usage.* attributes",
6543
+ "Prompts (opt-in): user prompt text via IDE_OTEL_CAPTURE_TEXT \u2014 har telemetry on --prompts (also fills Mission Control purpose)",
6544
+ "Fallback: har control sync harvests local Claude/Codex session files when hooks telemetry is missing"
6545
+ ];
6546
+ }
6547
+ });
6548
+
6549
+ // src/core/otel-hooks.ts
6550
+ var otel_hooks_exports = {};
6551
+ __export(otel_hooks_exports, {
6552
+ OTEL_HOOKS_AGENTS: () => OTEL_HOOKS_AGENTS,
6553
+ OTEL_HOOKS_PACKAGE: () => OTEL_HOOKS_PACKAGE,
6554
+ buildOtelHooksConfig: () => buildOtelHooksConfig,
6555
+ disableOtelHooksExport: () => disableOtelHooksExport,
6556
+ ensureOtelHooks: () => ensureOtelHooks,
6557
+ getOtelHooksConfigPath: () => getOtelHooksConfigPath,
6558
+ getOtelHooksHome: () => getOtelHooksHome,
6559
+ getOtelHooksWrapperPath: () => getOtelHooksWrapperPath,
6560
+ rewriteHookCommandsToWrapper: () => rewriteHookCommandsToWrapper,
6561
+ writeOtelHooksConfig: () => writeOtelHooksConfig,
6562
+ writeOtelHooksWrapper: () => writeOtelHooksWrapper
6563
+ });
6564
+ function getOtelHooksHome() {
6565
+ if (process.env.HAR_OTEL_HOOKS_HOME) {
6566
+ return path41.resolve(process.env.HAR_OTEL_HOOKS_HOME);
6567
+ }
6568
+ return path41.join(os12.homedir(), ".har", "otel-hooks");
6569
+ }
6570
+ function getOtelHooksConfigPath(hooksHome = getOtelHooksHome()) {
6571
+ return path41.join(hooksHome, "otel_config.json");
6572
+ }
6573
+ function getOtelHooksWrapperPath(hooksHome = getOtelHooksHome()) {
6574
+ return path41.join(hooksHome, "run-otel-hook.sh");
6575
+ }
6576
+ function buildOtelHooksConfig(options) {
6577
+ const enabled = options?.enabled ?? isTelemetryEnabled();
6578
+ const signals = getTelemetrySignals();
6579
+ const apiUrl = (options?.apiUrl ?? getControlApiUrl()).replace(/\/$/, "");
6580
+ return {
6581
+ OTEL_EXPORTER_OTLP_ENDPOINT: enabled ? `${apiUrl}/api/otel/v1/traces` : null,
6582
+ OTEL_EXPORTER_OTLP_PROTOCOL: "http/json",
6583
+ OTEL_SERVICE_NAME: "har-ide-agent",
6584
+ IDE_OTEL_BATCH_ON_STOP: "true",
6585
+ IDE_OTEL_ENABLE_LOGS: enabled && signals.logs ? "true" : "false",
6586
+ IDE_OTEL_LOG_ALL_EVENTS: enabled && signals.logs ? "true" : "false",
6587
+ IDE_OTEL_CAPTURE_TEXT: enabled && signals.prompts ? "true" : "false",
6588
+ IDE_OTEL_MASK_PROMPTS: "false",
6589
+ OTEL_RESOURCE_ATTRIBUTES: options?.resourceAttributes ?? null
6590
+ };
6591
+ }
6592
+ function writeOtelHooksConfig(config2, hooksHome = getOtelHooksHome()) {
6593
+ fs39.mkdirSync(hooksHome, { recursive: true });
6594
+ const configPath = getOtelHooksConfigPath(hooksHome);
6595
+ const payload = {
6596
+ _comment: "Managed by har telemetry \u2014 do not edit by hand; use: har telemetry on|off",
6597
+ ...config2
6598
+ };
6599
+ fs39.writeFileSync(configPath, `${JSON.stringify(payload, null, 2)}
6600
+ `);
6601
+ return configPath;
6602
+ }
6603
+ function writeOtelHooksWrapper(otelHookCommand, hooksHome = getOtelHooksHome()) {
6604
+ fs39.mkdirSync(hooksHome, { recursive: true });
6605
+ const wrapperPath = getOtelHooksWrapperPath(hooksHome);
6606
+ const script = `#!/usr/bin/env bash
6607
+ # Managed by har telemetry \u2014 invokes opentelemetry-hooks with HAR config home.
6608
+ set -euo pipefail
6609
+ export IDE_OTEL_HOOK_HOME="${hooksHome}"
6610
+ exec "${otelHookCommand}" "$@"
6611
+ `;
6612
+ fs39.writeFileSync(wrapperPath, script, { mode: 493 });
6613
+ try {
6614
+ fs39.chmodSync(wrapperPath, 493);
6615
+ } catch {
6616
+ }
6617
+ return wrapperPath;
6618
+ }
6619
+ function commandExists(command2) {
6620
+ const result = (0, import_child_process9.spawnSync)("sh", ["-c", `command -v ${JSON.stringify(command2)}`], {
6621
+ encoding: "utf8"
6622
+ });
6623
+ return result.status === 0 && Boolean(result.stdout?.trim());
6624
+ }
6625
+ function resolveOtelHookCommand() {
6626
+ if (commandExists("otel-hook")) {
6627
+ const which = (0, import_child_process9.spawnSync)("sh", ["-c", "command -v otel-hook"], { encoding: "utf8" });
6628
+ return which.stdout?.trim() || "otel-hook";
6629
+ }
6630
+ return null;
6631
+ }
6632
+ function tryInstallPackage() {
6633
+ if (commandExists("uv")) {
6634
+ const result = (0, import_child_process9.spawnSync)(
6635
+ "uv",
6636
+ ["tool", "install", "--force", OTEL_HOOKS_PACKAGE],
6637
+ { encoding: "utf8", timeout: 18e4 }
6638
+ );
6639
+ if (result.status === 0) {
6640
+ return { ok: true, detail: `uv tool install ${OTEL_HOOKS_PACKAGE}` };
6641
+ }
6642
+ }
6643
+ if (commandExists("pipx")) {
6644
+ const result = (0, import_child_process9.spawnSync)("pipx", ["install", "--force", OTEL_HOOKS_PACKAGE], {
6645
+ encoding: "utf8",
6646
+ timeout: 18e4
6647
+ });
6648
+ if (result.status === 0) {
6649
+ return { ok: true, detail: `pipx install ${OTEL_HOOKS_PACKAGE}` };
6650
+ }
6651
+ return {
6652
+ ok: false,
6653
+ detail: `pipx install failed: ${(result.stderr || result.stdout || "").trim().slice(0, 400)}`
6654
+ };
6655
+ }
6656
+ if (commandExists("pip3") || commandExists("pip")) {
6657
+ const pip = commandExists("pip3") ? "pip3" : "pip";
6658
+ const result = (0, import_child_process9.spawnSync)(
6659
+ pip,
6660
+ ["install", "--user", OTEL_HOOKS_PACKAGE],
6661
+ { encoding: "utf8", timeout: 18e4 }
6662
+ );
6663
+ if (result.status === 0) {
6664
+ return { ok: true, detail: `${pip} install --user ${OTEL_HOOKS_PACKAGE}` };
6665
+ }
6666
+ return {
6667
+ ok: false,
6668
+ detail: `${pip} install failed: ${(result.stderr || result.stdout || "").trim().slice(0, 400)}`
6669
+ };
6670
+ }
6671
+ return {
6672
+ ok: false,
6673
+ detail: "Neither uv, pipx, nor pip3/pip found; install Python tooling to use opentelemetry-hooks"
6674
+ };
6675
+ }
6676
+ function runSetupAgent(agent, otelHookCommand, hooksHome) {
6677
+ const result = (0, import_child_process9.spawnSync)(otelHookCommand, ["setup", "--agent", agent], {
6678
+ encoding: "utf8",
6679
+ env: {
6680
+ ...process.env,
6681
+ IDE_OTEL_HOOK_HOME: hooksHome
6682
+ },
6683
+ timeout: 6e4
6684
+ });
6685
+ if (result.status === 0) {
6686
+ return { ok: true, detail: `setup --agent ${agent}` };
6687
+ }
6688
+ return {
6689
+ ok: false,
6690
+ detail: `setup --agent ${agent} failed: ${(result.stderr || result.stdout || "").trim().slice(0, 400)}`
6691
+ };
6692
+ }
6693
+ function rewriteHookCommandsToWrapper(filePath, wrapperPath) {
6694
+ if (!fs39.existsSync(filePath)) return false;
6695
+ let raw;
6696
+ try {
6697
+ raw = fs39.readFileSync(filePath, "utf8");
6698
+ } catch {
6699
+ return false;
6700
+ }
6701
+ let parsed;
6702
+ try {
6703
+ parsed = JSON.parse(raw);
6704
+ } catch {
6705
+ return false;
6706
+ }
6707
+ if (!parsed || typeof parsed !== "object") return false;
6708
+ const quoted = JSON.stringify(wrapperPath);
6709
+ const rewriteCommand = (command2) => {
6710
+ if (!command2.includes("otel-hook") && !command2.includes("otel_hook.py")) return command2;
6711
+ if (command2.includes(wrapperPath)) return command2;
6712
+ return command2.replace(
6713
+ /(?:^|[;&|]\s*)(?:env\s+[^=\s]+=\S+\s+)*(?:python3?\s+)?(?:\S*otel[_-]hook\S*)/,
6714
+ (match) => match.replace(/(?:python3?\s+)?(?:\S*otel[_-]hook\S*)/, quoted.slice(1, -1))
6715
+ );
6716
+ };
6717
+ const walk = (node) => {
6718
+ let changed = false;
6719
+ if (Array.isArray(node)) {
6720
+ for (const item of node) changed = walk(item) || changed;
6721
+ return changed;
6722
+ }
6723
+ if (!node || typeof node !== "object") return false;
6724
+ const obj = node;
6725
+ if (typeof obj.command === "string") {
6726
+ const next = rewriteCommand(obj.command);
6727
+ if (next !== obj.command) {
6728
+ obj.command = next;
6729
+ changed = true;
6730
+ }
6731
+ }
6732
+ for (const value of Object.values(obj)) changed = walk(value) || changed;
6733
+ return changed;
6734
+ };
6735
+ if (!walk(parsed)) return false;
6736
+ fs39.writeFileSync(filePath, `${JSON.stringify(parsed, null, 2)}
6737
+ `);
6738
+ return true;
6739
+ }
6740
+ function rewriteKnownHookRegistrations(wrapperPath) {
6741
+ const home = os12.homedir();
6742
+ const candidates = [
6743
+ path41.join(home, ".cursor", "hooks.json"),
6744
+ path41.join(home, ".claude", "settings.json"),
6745
+ path41.join(home, ".codex", "hooks.json")
6746
+ ];
6747
+ for (const filePath of candidates) {
6748
+ try {
6749
+ rewriteHookCommandsToWrapper(filePath, wrapperPath);
6750
+ } catch {
6751
+ }
6752
+ }
6753
+ }
6754
+ function ensureOtelHooks(options) {
6755
+ const hooksHome = getOtelHooksHome();
6756
+ const errors = [];
6757
+ const agentsSetup = [];
6758
+ fs39.mkdirSync(hooksHome, { recursive: true });
6759
+ let otelHookCommand = resolveOtelHookCommand();
6760
+ if (!otelHookCommand) {
6761
+ const installed = tryInstallPackage();
6762
+ if (!installed.ok) {
6763
+ errors.push(installed.detail);
6764
+ const configPath2 = writeOtelHooksConfig(
6765
+ buildOtelHooksConfig({ resourceAttributes: options?.resourceAttributes }),
6766
+ hooksHome
6767
+ );
6768
+ return {
6769
+ ok: false,
6770
+ hooksHome,
6771
+ configPath: configPath2,
6772
+ wrapperPath: getOtelHooksWrapperPath(hooksHome),
6773
+ otelHookCommand: null,
6774
+ warning: installed.detail,
6775
+ agentsSetup,
6776
+ errors
6777
+ };
6778
+ }
6779
+ otelHookCommand = resolveOtelHookCommand();
6780
+ if (!otelHookCommand) {
6781
+ errors.push("opentelemetry-hooks installed but otel-hook is not on PATH");
6782
+ const configPath2 = writeOtelHooksConfig(
6783
+ buildOtelHooksConfig({ resourceAttributes: options?.resourceAttributes }),
6784
+ hooksHome
6785
+ );
6786
+ return {
6787
+ ok: false,
6788
+ hooksHome,
6789
+ configPath: configPath2,
6790
+ wrapperPath: getOtelHooksWrapperPath(hooksHome),
6791
+ otelHookCommand: null,
6792
+ warning: errors[errors.length - 1],
6793
+ agentsSetup,
6794
+ errors
6795
+ };
6796
+ }
6797
+ }
6798
+ const configPath = writeOtelHooksConfig(
6799
+ buildOtelHooksConfig({ resourceAttributes: options?.resourceAttributes }),
6800
+ hooksHome
6801
+ );
6802
+ const wrapperPath = writeOtelHooksWrapper(otelHookCommand, hooksHome);
6803
+ if (options?.setupAgents !== false && isTelemetryEnabled()) {
6804
+ for (const agent of OTEL_HOOKS_AGENTS) {
6805
+ const setup = runSetupAgent(agent, otelHookCommand, hooksHome);
6806
+ if (setup.ok) agentsSetup.push(agent);
6807
+ else errors.push(setup.detail);
6808
+ }
6809
+ rewriteKnownHookRegistrations(wrapperPath);
6810
+ }
6811
+ const ok = errors.length === 0;
6812
+ return {
6813
+ ok,
6814
+ hooksHome,
6815
+ configPath,
6816
+ wrapperPath,
6817
+ otelHookCommand,
6818
+ message: ok ? `opentelemetry-hooks ready (${agentsSetup.join(", ") || "config only"}) \u2192 ${configPath}` : void 0,
6819
+ warning: errors.length ? errors.join("; ") : void 0,
6820
+ agentsSetup,
6821
+ errors
6822
+ };
6823
+ }
6824
+ function disableOtelHooksExport() {
6825
+ return writeOtelHooksConfig(buildOtelHooksConfig({ enabled: false }));
6826
+ }
6827
+ var import_child_process9, fs39, os12, path41, OTEL_HOOKS_PACKAGE, OTEL_HOOKS_AGENTS;
6828
+ var init_otel_hooks = __esm({
6829
+ "src/core/otel-hooks.ts"() {
6830
+ "use strict";
6831
+ import_child_process9 = require("child_process");
6832
+ fs39 = __toESM(require("fs"));
6833
+ os12 = __toESM(require("os"));
6834
+ path41 = __toESM(require("path"));
6835
+ init_control_config();
6836
+ init_telemetry_config();
6837
+ OTEL_HOOKS_PACKAGE = "opentelemetry-hooks==0.14.0";
6838
+ OTEL_HOOKS_AGENTS = ["cursor", "claude", "codex"];
6839
+ }
6840
+ });
6841
+
6436
6842
  // node_modules/ajv/dist/compile/codegen/code.js
6437
6843
  var require_code = __commonJS({
6438
6844
  "node_modules/ajv/dist/compile/codegen/code.js"(exports2) {
@@ -9384,7 +9790,7 @@ var require_compile = __commonJS({
9384
9790
  const schOrFunc = root.refs[ref];
9385
9791
  if (schOrFunc)
9386
9792
  return schOrFunc;
9387
- let _sch = resolve33.call(this, root, ref);
9793
+ let _sch = resolve35.call(this, root, ref);
9388
9794
  if (_sch === void 0) {
9389
9795
  const schema = (_a3 = root.localRefs) === null || _a3 === void 0 ? void 0 : _a3[ref];
9390
9796
  const { schemaId } = this.opts;
@@ -9411,7 +9817,7 @@ var require_compile = __commonJS({
9411
9817
  function sameSchemaEnv(s1, s2) {
9412
9818
  return s1.schema === s2.schema && s1.root === s2.root && s1.baseId === s2.baseId;
9413
9819
  }
9414
- function resolve33(root, ref) {
9820
+ function resolve35(root, ref) {
9415
9821
  let sch;
9416
9822
  while (typeof (sch = this.refs[ref]) == "string")
9417
9823
  ref = sch;
@@ -9629,8 +10035,8 @@ var require_utils2 = __commonJS({
9629
10035
  }
9630
10036
  return ind;
9631
10037
  }
9632
- function removeDotSegments(path48) {
9633
- let input = path48;
10038
+ function removeDotSegments(path50) {
10039
+ let input = path50;
9634
10040
  const output = [];
9635
10041
  let nextSlash = -1;
9636
10042
  let len = 0;
@@ -9882,8 +10288,8 @@ var require_schemes = __commonJS({
9882
10288
  wsComponent.secure = void 0;
9883
10289
  }
9884
10290
  if (wsComponent.resourceName) {
9885
- const [path48, query] = wsComponent.resourceName.split("?");
9886
- wsComponent.path = path48 && path48 !== "/" ? path48 : void 0;
10291
+ const [path50, query] = wsComponent.resourceName.split("?");
10292
+ wsComponent.path = path50 && path50 !== "/" ? path50 : void 0;
9887
10293
  wsComponent.query = query;
9888
10294
  wsComponent.resourceName = void 0;
9889
10295
  }
@@ -10042,55 +10448,55 @@ var require_fast_uri = __commonJS({
10042
10448
  }
10043
10449
  return uri;
10044
10450
  }
10045
- function resolve33(baseURI, relativeURI, options) {
10451
+ function resolve35(baseURI, relativeURI, options) {
10046
10452
  const schemelessOptions = options ? Object.assign({ scheme: "null" }, options) : { scheme: "null" };
10047
10453
  const resolved = resolveComponent(parse4(baseURI, schemelessOptions), parse4(relativeURI, schemelessOptions), schemelessOptions, true);
10048
10454
  schemelessOptions.skipEscape = true;
10049
10455
  return serialize(resolved, schemelessOptions);
10050
10456
  }
10051
- function resolveComponent(base, relative2, options, skipNormalization) {
10457
+ function resolveComponent(base, relative3, options, skipNormalization) {
10052
10458
  const target = {};
10053
10459
  if (!skipNormalization) {
10054
10460
  base = parse4(serialize(base, options), options);
10055
- relative2 = parse4(serialize(relative2, options), options);
10461
+ relative3 = parse4(serialize(relative3, options), options);
10056
10462
  }
10057
10463
  options = options || {};
10058
- if (!options.tolerant && relative2.scheme) {
10059
- target.scheme = relative2.scheme;
10060
- target.userinfo = relative2.userinfo;
10061
- target.host = relative2.host;
10062
- target.port = relative2.port;
10063
- target.path = removeDotSegments(relative2.path || "");
10064
- target.query = relative2.query;
10464
+ if (!options.tolerant && relative3.scheme) {
10465
+ target.scheme = relative3.scheme;
10466
+ target.userinfo = relative3.userinfo;
10467
+ target.host = relative3.host;
10468
+ target.port = relative3.port;
10469
+ target.path = removeDotSegments(relative3.path || "");
10470
+ target.query = relative3.query;
10065
10471
  } else {
10066
- if (relative2.userinfo !== void 0 || relative2.host !== void 0 || relative2.port !== void 0) {
10067
- target.userinfo = relative2.userinfo;
10068
- target.host = relative2.host;
10069
- target.port = relative2.port;
10070
- target.path = removeDotSegments(relative2.path || "");
10071
- target.query = relative2.query;
10472
+ if (relative3.userinfo !== void 0 || relative3.host !== void 0 || relative3.port !== void 0) {
10473
+ target.userinfo = relative3.userinfo;
10474
+ target.host = relative3.host;
10475
+ target.port = relative3.port;
10476
+ target.path = removeDotSegments(relative3.path || "");
10477
+ target.query = relative3.query;
10072
10478
  } else {
10073
- if (!relative2.path) {
10479
+ if (!relative3.path) {
10074
10480
  target.path = base.path;
10075
- if (relative2.query !== void 0) {
10076
- target.query = relative2.query;
10481
+ if (relative3.query !== void 0) {
10482
+ target.query = relative3.query;
10077
10483
  } else {
10078
10484
  target.query = base.query;
10079
10485
  }
10080
10486
  } else {
10081
- if (relative2.path[0] === "/") {
10082
- target.path = removeDotSegments(relative2.path);
10487
+ if (relative3.path[0] === "/") {
10488
+ target.path = removeDotSegments(relative3.path);
10083
10489
  } else {
10084
10490
  if ((base.userinfo !== void 0 || base.host !== void 0 || base.port !== void 0) && !base.path) {
10085
- target.path = "/" + relative2.path;
10491
+ target.path = "/" + relative3.path;
10086
10492
  } else if (!base.path) {
10087
- target.path = relative2.path;
10493
+ target.path = relative3.path;
10088
10494
  } else {
10089
- target.path = base.path.slice(0, base.path.lastIndexOf("/") + 1) + relative2.path;
10495
+ target.path = base.path.slice(0, base.path.lastIndexOf("/") + 1) + relative3.path;
10090
10496
  }
10091
10497
  target.path = removeDotSegments(target.path);
10092
10498
  }
10093
- target.query = relative2.query;
10499
+ target.query = relative3.query;
10094
10500
  }
10095
10501
  target.userinfo = base.userinfo;
10096
10502
  target.host = base.host;
@@ -10098,7 +10504,7 @@ var require_fast_uri = __commonJS({
10098
10504
  }
10099
10505
  target.scheme = base.scheme;
10100
10506
  }
10101
- target.fragment = relative2.fragment;
10507
+ target.fragment = relative3.fragment;
10102
10508
  return target;
10103
10509
  }
10104
10510
  function equal(uriA, uriB, options) {
@@ -10300,7 +10706,7 @@ var require_fast_uri = __commonJS({
10300
10706
  var fastUri = {
10301
10707
  SCHEMES,
10302
10708
  normalize: normalize3,
10303
- resolve: resolve33,
10709
+ resolve: resolve35,
10304
10710
  resolveComponent,
10305
10711
  equal,
10306
10712
  serialize,
@@ -13276,12 +13682,12 @@ var require_dist = __commonJS({
13276
13682
  throw new Error(`Unknown format "${name}"`);
13277
13683
  return f2;
13278
13684
  };
13279
- function addFormats(ajv, list, fs41, exportName) {
13685
+ function addFormats(ajv, list, fs42, exportName) {
13280
13686
  var _a3;
13281
13687
  var _b2;
13282
13688
  (_a3 = (_b2 = ajv.opts.code).formats) !== null && _a3 !== void 0 ? _a3 : _b2.formats = (0, codegen_1._)`require("ajv-formats/dist/formats").${exportName}`;
13283
13689
  for (const f2 of list)
13284
- ajv.addFormat(f2, fs41[f2]);
13690
+ ajv.addFormat(f2, fs42[f2]);
13285
13691
  }
13286
13692
  module2.exports = exports2 = formatsPlugin;
13287
13693
  Object.defineProperty(exports2, "__esModule", { value: true });
@@ -14565,11 +14971,11 @@ var parser = new YargsParser({
14565
14971
  resolve: import_path2.resolve,
14566
14972
  // TODO: figure out a way to combine ESM and CJS coverage, such that
14567
14973
  // we can exercise all the lines below:
14568
- require: (path48) => {
14974
+ require: (path50) => {
14569
14975
  if (typeof require !== "undefined") {
14570
- return require(path48);
14571
- } else if (path48.match(/\.json$/)) {
14572
- return JSON.parse((0, import_fs2.readFileSync)(path48, "utf8"));
14976
+ return require(path50);
14977
+ } else if (path50.match(/\.json$/)) {
14978
+ return JSON.parse((0, import_fs2.readFileSync)(path50, "utf8"));
14573
14979
  } else {
14574
14980
  throw Error("only .json config files are supported in ESM");
14575
14981
  }
@@ -15912,13 +16318,13 @@ function usage(yargs, shim3) {
15912
16318
  };
15913
16319
  self.stringifiedValues = function stringifiedValues(values, separator) {
15914
16320
  let string3 = "";
15915
- const sep5 = separator || ", ";
16321
+ const sep6 = separator || ", ";
15916
16322
  const array2 = [].concat(values);
15917
16323
  if (!values || !array2.length)
15918
16324
  return string3;
15919
16325
  array2.forEach((value) => {
15920
16326
  if (string3.length)
15921
- string3 += sep5;
16327
+ string3 += sep6;
15922
16328
  string3 += JSON.stringify(value);
15923
16329
  });
15924
16330
  return string3;
@@ -17095,12 +17501,12 @@ var YargsInstance = class {
17095
17501
  async getCompletion(args, done) {
17096
17502
  argsert("<array> [function]", [args, done], arguments.length);
17097
17503
  if (!done) {
17098
- return new Promise((resolve33, reject) => {
17504
+ return new Promise((resolve35, reject) => {
17099
17505
  __classPrivateFieldGet(this, _YargsInstance_completion, "f").getCompletion(args, (err, completions) => {
17100
17506
  if (err)
17101
17507
  reject(err);
17102
17508
  else
17103
- resolve33(completions);
17509
+ resolve35(completions);
17104
17510
  });
17105
17511
  });
17106
17512
  } else {
@@ -19250,8 +19656,8 @@ function getErrorMap() {
19250
19656
 
19251
19657
  // node_modules/zod/v3/helpers/parseUtil.js
19252
19658
  var makeIssue = (params) => {
19253
- const { data, path: path48, errorMaps, issueData } = params;
19254
- const fullPath = [...path48, ...issueData.path || []];
19659
+ const { data, path: path50, errorMaps, issueData } = params;
19660
+ const fullPath = [...path50, ...issueData.path || []];
19255
19661
  const fullIssue = {
19256
19662
  ...issueData,
19257
19663
  path: fullPath
@@ -19367,11 +19773,11 @@ var errorUtil;
19367
19773
 
19368
19774
  // node_modules/zod/v3/types.js
19369
19775
  var ParseInputLazyPath = class {
19370
- constructor(parent, value, path48, key) {
19776
+ constructor(parent, value, path50, key) {
19371
19777
  this._cachedPath = [];
19372
19778
  this.parent = parent;
19373
19779
  this.data = value;
19374
- this._path = path48;
19780
+ this._path = path50;
19375
19781
  this._key = key;
19376
19782
  }
19377
19783
  get path() {
@@ -22992,7 +23398,6 @@ var SlotRegistryEntrySchema = external_exports.object({
22992
23398
  /** Random per-session chars; absent in root mode. */
22993
23399
  suffix: external_exports.string().optional(),
22994
23400
  createdAt: external_exports.string(),
22995
- purpose: external_exports.string().optional(),
22996
23401
  ports: external_exports.record(external_exports.number()).optional(),
22997
23402
  previewUrls: external_exports.record(external_exports.string()).optional(),
22998
23403
  status: external_exports.enum(["starting", "active", "failed", "completed"]).default("active"),
@@ -23038,6 +23443,7 @@ var AgentSlotStatusSchema = external_exports.object({
23038
23443
  baseBranch: external_exports.string().optional(),
23039
23444
  baseCommit: external_exports.string().optional(),
23040
23445
  sessionCreatedAt: external_exports.string().optional(),
23446
+ /** Derived from first captured user prompt (OTEL hooks); not set at launch. */
23041
23447
  purpose: external_exports.string().optional(),
23042
23448
  sessionStatus: external_exports.enum(["starting", "active", "failed", "completed"]).optional(),
23043
23449
  lastError: external_exports.string().optional(),
@@ -23106,7 +23512,7 @@ var ValidationRecordSchema = external_exports.object({
23106
23512
  var SyncValidationsInputSchema = external_exports.object({
23107
23513
  validations: external_exports.array(ValidationRecordSchema)
23108
23514
  });
23109
- var AgentToolSchema = external_exports.enum(["claude_code", "codex"]);
23515
+ var AgentToolSchema = external_exports.enum(["claude_code", "codex", "cursor"]);
23110
23516
  var UsageSourceSchema = external_exports.enum(["otel", "harvest"]);
23111
23517
  var AgentSessionUsageSchema = external_exports.object({
23112
23518
  sessionKey: external_exports.string().min(1),
@@ -23410,16 +23816,16 @@ async function handleAgentSkills(options) {
23410
23816
  }
23411
23817
  async function askYesNo(question) {
23412
23818
  const rl = readline.createInterface({ input: process.stdin, output: process.stderr });
23413
- return new Promise((resolve33) => {
23819
+ return new Promise((resolve35) => {
23414
23820
  process.stderr.write(`${question} `);
23415
23821
  rl.once("line", (answer) => {
23416
23822
  rl.close();
23417
23823
  const trimmed = answer.trim();
23418
23824
  if (trimmed === "") {
23419
- resolve33(true);
23825
+ resolve35(true);
23420
23826
  return;
23421
23827
  }
23422
- resolve33(/^y(es)?$/i.test(trimmed));
23828
+ resolve35(/^y(es)?$/i.test(trimmed));
23423
23829
  });
23424
23830
  });
23425
23831
  }
@@ -23498,7 +23904,7 @@ function handleRemove(argv) {
23498
23904
  }
23499
23905
 
23500
23906
  // src/cli/commands/env.ts
23501
- var path41 = __toESM(require("path"));
23907
+ var path43 = __toESM(require("path"));
23502
23908
 
23503
23909
  // src/core/harness.ts
23504
23910
  var fs20 = __toESM(require("fs"));
@@ -24277,13 +24683,13 @@ var MultipartBody = class {
24277
24683
  // node_modules/@anthropic-ai/sdk/_shims/node-runtime.mjs
24278
24684
  var import_web = require("node:stream/web");
24279
24685
  var fileFromPathWarned = false;
24280
- async function fileFromPath3(path48, ...args) {
24686
+ async function fileFromPath3(path50, ...args) {
24281
24687
  const { fileFromPath: _fileFromPath } = await Promise.resolve().then(() => (init_fileFromPath(), fileFromPath_exports));
24282
24688
  if (!fileFromPathWarned) {
24283
- console.warn(`fileFromPath is deprecated; use fs.createReadStream(${JSON.stringify(path48)}) instead`);
24689
+ console.warn(`fileFromPath is deprecated; use fs.createReadStream(${JSON.stringify(path50)}) instead`);
24284
24690
  fileFromPathWarned = true;
24285
24691
  }
24286
- return await _fileFromPath(path48, ...args);
24692
+ return await _fileFromPath(path50, ...args);
24287
24693
  }
24288
24694
  var defaultHttpAgent = new import_agentkeepalive.default({ keepAlive: true, timeout: 5 * 60 * 1e3 });
24289
24695
  var defaultHttpsAgent = new import_agentkeepalive.default.HttpsAgent({ keepAlive: true, timeout: 5 * 60 * 1e3 });
@@ -24779,8 +25185,8 @@ async function defaultParseResponse(props) {
24779
25185
  }
24780
25186
  var APIPromise = class _APIPromise extends Promise {
24781
25187
  constructor(responsePromise, parseResponse = defaultParseResponse) {
24782
- super((resolve33) => {
24783
- resolve33(null);
25188
+ super((resolve35) => {
25189
+ resolve35(null);
24784
25190
  });
24785
25191
  this.responsePromise = responsePromise;
24786
25192
  this.parseResponse = parseResponse;
@@ -24880,29 +25286,29 @@ var APIClient = class {
24880
25286
  defaultIdempotencyKey() {
24881
25287
  return `stainless-node-retry-${uuid4()}`;
24882
25288
  }
24883
- get(path48, opts) {
24884
- return this.methodRequest("get", path48, opts);
25289
+ get(path50, opts) {
25290
+ return this.methodRequest("get", path50, opts);
24885
25291
  }
24886
- post(path48, opts) {
24887
- return this.methodRequest("post", path48, opts);
25292
+ post(path50, opts) {
25293
+ return this.methodRequest("post", path50, opts);
24888
25294
  }
24889
- patch(path48, opts) {
24890
- return this.methodRequest("patch", path48, opts);
25295
+ patch(path50, opts) {
25296
+ return this.methodRequest("patch", path50, opts);
24891
25297
  }
24892
- put(path48, opts) {
24893
- return this.methodRequest("put", path48, opts);
25298
+ put(path50, opts) {
25299
+ return this.methodRequest("put", path50, opts);
24894
25300
  }
24895
- delete(path48, opts) {
24896
- return this.methodRequest("delete", path48, opts);
25301
+ delete(path50, opts) {
25302
+ return this.methodRequest("delete", path50, opts);
24897
25303
  }
24898
- methodRequest(method, path48, opts) {
25304
+ methodRequest(method, path50, opts) {
24899
25305
  return this.request(Promise.resolve(opts).then(async (opts2) => {
24900
25306
  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;
24901
- return { method, path: path48, ...opts2, body };
25307
+ return { method, path: path50, ...opts2, body };
24902
25308
  }));
24903
25309
  }
24904
- getAPIList(path48, Page, opts) {
24905
- return this.requestAPIList(Page, { method: "get", path: path48, ...opts });
25310
+ getAPIList(path50, Page, opts) {
25311
+ return this.requestAPIList(Page, { method: "get", path: path50, ...opts });
24906
25312
  }
24907
25313
  calculateContentLength(body) {
24908
25314
  if (typeof body === "string") {
@@ -24920,10 +25326,10 @@ var APIClient = class {
24920
25326
  return null;
24921
25327
  }
24922
25328
  buildRequest(options) {
24923
- const { method, path: path48, query, headers = {} } = options;
25329
+ const { method, path: path50, query, headers = {} } = options;
24924
25330
  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;
24925
25331
  const contentLength = this.calculateContentLength(body);
24926
- const url = this.buildURL(path48, query);
25332
+ const url = this.buildURL(path50, query);
24927
25333
  if ("timeout" in options)
24928
25334
  validatePositiveInteger("timeout", options.timeout);
24929
25335
  const timeout = options.timeout ?? this.timeout;
@@ -25032,8 +25438,8 @@ var APIClient = class {
25032
25438
  const request = this.makeRequest(options, null);
25033
25439
  return new PagePromise(this, request, Page);
25034
25440
  }
25035
- buildURL(path48, query) {
25036
- const url = isAbsoluteURL(path48) ? new URL(path48) : new URL(this.baseURL + (this.baseURL.endsWith("/") && path48.startsWith("/") ? path48.slice(1) : path48));
25441
+ buildURL(path50, query) {
25442
+ const url = isAbsoluteURL(path50) ? new URL(path50) : new URL(this.baseURL + (this.baseURL.endsWith("/") && path50.startsWith("/") ? path50.slice(1) : path50));
25037
25443
  const defaultQuery = this.defaultQuery();
25038
25444
  if (!isEmptyObj(defaultQuery)) {
25039
25445
  query = { ...defaultQuery, ...query };
@@ -25318,7 +25724,7 @@ var startsWithSchemeRegexp = new RegExp("^(?:[a-z]+:)?//", "i");
25318
25724
  var isAbsoluteURL = (url) => {
25319
25725
  return startsWithSchemeRegexp.test(url);
25320
25726
  };
25321
- var sleep = (ms) => new Promise((resolve33) => setTimeout(resolve33, ms));
25727
+ var sleep = (ms) => new Promise((resolve35) => setTimeout(resolve35, ms));
25322
25728
  var validatePositiveInteger = (name, n2) => {
25323
25729
  if (typeof n2 !== "number" || !Number.isInteger(n2)) {
25324
25730
  throw new AnthropicError(`${name} must be an integer`);
@@ -25809,12 +26215,12 @@ var PromptCachingBetaMessageStream = class _PromptCachingBetaMessageStream {
25809
26215
  }
25810
26216
  return this._emit("error", new AnthropicError(String(error3)));
25811
26217
  });
25812
- __classPrivateFieldSet7(this, _PromptCachingBetaMessageStream_connectedPromise, new Promise((resolve33, reject) => {
25813
- __classPrivateFieldSet7(this, _PromptCachingBetaMessageStream_resolveConnectedPromise, resolve33, "f");
26218
+ __classPrivateFieldSet7(this, _PromptCachingBetaMessageStream_connectedPromise, new Promise((resolve35, reject) => {
26219
+ __classPrivateFieldSet7(this, _PromptCachingBetaMessageStream_resolveConnectedPromise, resolve35, "f");
25814
26220
  __classPrivateFieldSet7(this, _PromptCachingBetaMessageStream_rejectConnectedPromise, reject, "f");
25815
26221
  }), "f");
25816
- __classPrivateFieldSet7(this, _PromptCachingBetaMessageStream_endPromise, new Promise((resolve33, reject) => {
25817
- __classPrivateFieldSet7(this, _PromptCachingBetaMessageStream_resolveEndPromise, resolve33, "f");
26222
+ __classPrivateFieldSet7(this, _PromptCachingBetaMessageStream_endPromise, new Promise((resolve35, reject) => {
26223
+ __classPrivateFieldSet7(this, _PromptCachingBetaMessageStream_resolveEndPromise, resolve35, "f");
25818
26224
  __classPrivateFieldSet7(this, _PromptCachingBetaMessageStream_rejectEndPromise, reject, "f");
25819
26225
  }), "f");
25820
26226
  __classPrivateFieldGet8(this, _PromptCachingBetaMessageStream_connectedPromise, "f").catch(() => {
@@ -25943,11 +26349,11 @@ var PromptCachingBetaMessageStream = class _PromptCachingBetaMessageStream {
25943
26349
  * const message = await stream.emitted('message') // rejects if the stream errors
25944
26350
  */
25945
26351
  emitted(event) {
25946
- return new Promise((resolve33, reject) => {
26352
+ return new Promise((resolve35, reject) => {
25947
26353
  __classPrivateFieldSet7(this, _PromptCachingBetaMessageStream_catchingPromiseCreated, true, "f");
25948
26354
  if (event !== "error")
25949
26355
  this.once("error", reject);
25950
- this.once(event, resolve33);
26356
+ this.once(event, resolve35);
25951
26357
  });
25952
26358
  }
25953
26359
  async done() {
@@ -26174,7 +26580,7 @@ var PromptCachingBetaMessageStream = class _PromptCachingBetaMessageStream {
26174
26580
  if (done) {
26175
26581
  return { value: void 0, done: true };
26176
26582
  }
26177
- return new Promise((resolve33, reject) => readQueue.push({ resolve: resolve33, reject })).then((chunk2) => chunk2 ? { value: chunk2, done: false } : { value: void 0, done: true });
26583
+ return new Promise((resolve35, reject) => readQueue.push({ resolve: resolve35, reject })).then((chunk2) => chunk2 ? { value: chunk2, done: false } : { value: void 0, done: true });
26178
26584
  }
26179
26585
  const chunk = pushQueue.shift();
26180
26586
  return { value: chunk, done: false };
@@ -26322,12 +26728,12 @@ var MessageStream = class _MessageStream {
26322
26728
  }
26323
26729
  return this._emit("error", new AnthropicError(String(error3)));
26324
26730
  });
26325
- __classPrivateFieldSet8(this, _MessageStream_connectedPromise, new Promise((resolve33, reject) => {
26326
- __classPrivateFieldSet8(this, _MessageStream_resolveConnectedPromise, resolve33, "f");
26731
+ __classPrivateFieldSet8(this, _MessageStream_connectedPromise, new Promise((resolve35, reject) => {
26732
+ __classPrivateFieldSet8(this, _MessageStream_resolveConnectedPromise, resolve35, "f");
26327
26733
  __classPrivateFieldSet8(this, _MessageStream_rejectConnectedPromise, reject, "f");
26328
26734
  }), "f");
26329
- __classPrivateFieldSet8(this, _MessageStream_endPromise, new Promise((resolve33, reject) => {
26330
- __classPrivateFieldSet8(this, _MessageStream_resolveEndPromise, resolve33, "f");
26735
+ __classPrivateFieldSet8(this, _MessageStream_endPromise, new Promise((resolve35, reject) => {
26736
+ __classPrivateFieldSet8(this, _MessageStream_resolveEndPromise, resolve35, "f");
26331
26737
  __classPrivateFieldSet8(this, _MessageStream_rejectEndPromise, reject, "f");
26332
26738
  }), "f");
26333
26739
  __classPrivateFieldGet9(this, _MessageStream_connectedPromise, "f").catch(() => {
@@ -26456,11 +26862,11 @@ var MessageStream = class _MessageStream {
26456
26862
  * const message = await stream.emitted('message') // rejects if the stream errors
26457
26863
  */
26458
26864
  emitted(event) {
26459
- return new Promise((resolve33, reject) => {
26865
+ return new Promise((resolve35, reject) => {
26460
26866
  __classPrivateFieldSet8(this, _MessageStream_catchingPromiseCreated, true, "f");
26461
26867
  if (event !== "error")
26462
26868
  this.once("error", reject);
26463
- this.once(event, resolve33);
26869
+ this.once(event, resolve35);
26464
26870
  });
26465
26871
  }
26466
26872
  async done() {
@@ -26687,7 +27093,7 @@ var MessageStream = class _MessageStream {
26687
27093
  if (done) {
26688
27094
  return { value: void 0, done: true };
26689
27095
  }
26690
- return new Promise((resolve33, reject) => readQueue.push({ resolve: resolve33, reject })).then((chunk2) => chunk2 ? { value: chunk2, done: false } : { value: void 0, done: true });
27096
+ return new Promise((resolve35, reject) => readQueue.push({ resolve: resolve35, reject })).then((chunk2) => chunk2 ? { value: chunk2, done: false } : { value: void 0, done: true });
26691
27097
  }
26692
27098
  const chunk = pushQueue.shift();
26693
27099
  return { value: chunk, done: false };
@@ -26947,11 +27353,11 @@ async function promptApplyAgentMdProposal(repoPath) {
26947
27353
  }
26948
27354
  async function askYesNo2(question) {
26949
27355
  const rl = readline2.createInterface({ input: process.stdin, output: process.stderr });
26950
- return new Promise((resolve33) => {
27356
+ return new Promise((resolve35) => {
26951
27357
  process.stderr.write(`${question} `);
26952
27358
  rl.once("line", (answer) => {
26953
27359
  rl.close();
26954
- resolve33(/^y(es)?$/i.test(answer.trim()));
27360
+ resolve35(/^y(es)?$/i.test(answer.trim()));
26955
27361
  });
26956
27362
  });
26957
27363
  }
@@ -27181,7 +27587,7 @@ async function executeAuthoringToolCall(toolName, input, repoPath, harnessDir, c
27181
27587
  }
27182
27588
  async function askUser(question, options) {
27183
27589
  const rl = readline3.createInterface({ input: process.stdin, output: process.stderr });
27184
- return new Promise((resolve33) => {
27590
+ return new Promise((resolve35) => {
27185
27591
  process.stderr.write("\n");
27186
27592
  process.stderr.write(`\u2753 ${question}
27187
27593
  `);
@@ -27192,7 +27598,7 @@ async function askUser(question, options) {
27192
27598
  process.stderr.write("> ");
27193
27599
  rl.once("line", (answer) => {
27194
27600
  rl.close();
27195
- resolve33(answer.trim());
27601
+ resolve35(answer.trim());
27196
27602
  });
27197
27603
  });
27198
27604
  }
@@ -27336,7 +27742,7 @@ function runScript(scriptPath, args = [], options = {}) {
27336
27742
  function runScriptCapture(scriptPath, args = [], options = {}) {
27337
27743
  const stream = options.stream ?? false;
27338
27744
  const { stream: _stream, ...spawnOptions } = options;
27339
- return new Promise((resolve33) => {
27745
+ return new Promise((resolve35) => {
27340
27746
  const proc = (0, import_child_process.spawn)("bash", [scriptPath, ...args], {
27341
27747
  ...spawnOptions,
27342
27748
  stdio: ["inherit", "pipe", "pipe"]
@@ -27351,13 +27757,13 @@ function runScriptCapture(scriptPath, args = [], options = {}) {
27351
27757
  stderr += d2;
27352
27758
  if (stream) process.stderr.write(d2);
27353
27759
  });
27354
- proc.on("close", (code) => resolve33({ stdout, stderr, code: code ?? 0 }));
27760
+ proc.on("close", (code) => resolve35({ stdout, stderr, code: code ?? 0 }));
27355
27761
  });
27356
27762
  }
27357
27763
  function runShellCommand(command2, options = {}) {
27358
27764
  const stream = options.stream ?? false;
27359
27765
  const { stream: _stream, ...spawnOptions } = options;
27360
- return new Promise((resolve33) => {
27766
+ return new Promise((resolve35) => {
27361
27767
  const proc = (0, import_child_process.spawn)("bash", ["-lc", command2], {
27362
27768
  ...spawnOptions,
27363
27769
  stdio: ["inherit", "pipe", "pipe"]
@@ -27372,7 +27778,7 @@ function runShellCommand(command2, options = {}) {
27372
27778
  stderr += d2;
27373
27779
  if (stream) process.stderr.write(d2);
27374
27780
  });
27375
- proc.on("close", (code) => resolve33({ stdout, stderr, code: code ?? 0 }));
27781
+ proc.on("close", (code) => resolve35({ stdout, stderr, code: code ?? 0 }));
27376
27782
  });
27377
27783
  }
27378
27784
 
@@ -28536,16 +28942,16 @@ async function promptScaffoldCursorRule(exists) {
28536
28942
  }
28537
28943
  async function askYesNo3(question) {
28538
28944
  const rl = readline4.createInterface({ input: process.stdin, output: process.stderr });
28539
- return new Promise((resolve33) => {
28945
+ return new Promise((resolve35) => {
28540
28946
  process.stderr.write(`${question} `);
28541
28947
  rl.once("line", (answer) => {
28542
28948
  rl.close();
28543
28949
  const trimmed = answer.trim();
28544
28950
  if (trimmed === "") {
28545
- resolve33(true);
28951
+ resolve35(true);
28546
28952
  return;
28547
28953
  }
28548
- resolve33(/^y(es)?$/i.test(trimmed));
28954
+ resolve35(/^y(es)?$/i.test(trimmed));
28549
28955
  });
28550
28956
  });
28551
28957
  }
@@ -28663,7 +29069,6 @@ function buildLaunchFlagArgs(flags) {
28663
29069
  if (flags.confirmReplace) args.push("--replace");
28664
29070
  if (flags.force) args.push("--force");
28665
29071
  if (flags.resume) args.push("--resume");
28666
- if (flags.purpose) args.push(`--purpose=${flags.purpose}`);
28667
29072
  return args;
28668
29073
  }
28669
29074
  function resolveRepoPath(repoPath) {
@@ -28744,9 +29149,6 @@ function buildExecutionPlan(repoPath, stage, options) {
28744
29149
  const cwd = stage.cwd ? path24.resolve(resolvedRepo, stage.cwd) : resolvedRepo;
28745
29150
  const extraArgs = options.args ?? [];
28746
29151
  const launchFlagArgs = stage.kind === "launch" && options.launchFlags ? buildLaunchFlagArgs(options.launchFlags) : [];
28747
- if (stage.kind === "launch" && options.launchFlags?.purpose) {
28748
- mergedEnv.HAR_SESSION_PURPOSE = options.launchFlags.purpose;
28749
- }
28750
29152
  if (stage.command) {
28751
29153
  let shellCommand = substituteAgentId(stage.command, options.agentId);
28752
29154
  for (const extra of [...extraArgs, ...launchFlagArgs]) {
@@ -28820,16 +29222,16 @@ var LocalScriptExecutor = class {
28820
29222
  const walk = (dir, prefix) => {
28821
29223
  for (const entry of fs25.readdirSync(dir, { withFileTypes: true })) {
28822
29224
  const full = path24.join(dir, entry.name);
28823
- const relative2 = path24.join(prefix, entry.name);
29225
+ const relative3 = path24.join(prefix, entry.name);
28824
29226
  if (entry.isDirectory()) {
28825
- walk(full, relative2);
29227
+ walk(full, relative3);
28826
29228
  continue;
28827
29229
  }
28828
- if (filter?.stageId && !relative2.includes(filter.stageId)) continue;
29230
+ if (filter?.stageId && !relative3.includes(filter.stageId)) continue;
28829
29231
  const stat = fs25.statSync(full);
28830
29232
  entries.push({
28831
29233
  path: full,
28832
- relativePath: relative2,
29234
+ relativePath: relative3,
28833
29235
  sizeBytes: stat.size,
28834
29236
  modifiedAt: stat.mtime.toISOString()
28835
29237
  });
@@ -28842,44 +29244,86 @@ var LocalScriptExecutor = class {
28842
29244
  var localScriptExecutor = new LocalScriptExecutor();
28843
29245
 
28844
29246
  // src/core/control-sync.ts
28845
- var path38 = __toESM(require("path"));
29247
+ var path39 = __toESM(require("path"));
29248
+ init_control_config();
28846
29249
 
28847
- // src/core/control-config.ts
28848
- var DEFAULT_CONTROL_API_URL = "http://localhost:3847";
28849
- function getControlApiUrl() {
28850
- return process.env.HAR_CONTROL_API_URL ?? DEFAULT_CONTROL_API_URL;
29250
+ // src/core/control-registry.ts
29251
+ var fs27 = __toESM(require("fs"));
29252
+ var os4 = __toESM(require("os"));
29253
+ var path26 = __toESM(require("path"));
29254
+
29255
+ // src/core/control-repo-path.ts
29256
+ var fs26 = __toESM(require("fs"));
29257
+ var path25 = __toESM(require("path"));
29258
+ function tryGit(cwd, args) {
29259
+ if (!fs26.existsSync(cwd)) return void 0;
29260
+ const result = run(`git ${args}`, { cwd });
29261
+ return result.code === 0 ? result.stdout.trim() : void 0;
28851
29262
  }
28852
- function isControlEnabled() {
28853
- return process.env.HAR_CONTROL_DISABLED !== "true";
29263
+ function resolveMainWorkingTree(cwd) {
29264
+ const toplevel = tryGit(cwd, "rev-parse --show-toplevel");
29265
+ if (!toplevel) return void 0;
29266
+ const commonDirRaw = tryGit(cwd, "rev-parse --git-common-dir");
29267
+ const commonDir = commonDirRaw ? path25.resolve(cwd, commonDirRaw) : void 0;
29268
+ if (!commonDir) return path25.resolve(toplevel);
29269
+ if (path25.basename(commonDir) === ".git") {
29270
+ return path25.dirname(commonDir);
29271
+ }
29272
+ const porcelain = tryGit(cwd, "worktree list --porcelain");
29273
+ if (porcelain) {
29274
+ for (const line of porcelain.split("\n")) {
29275
+ if (line.startsWith("worktree ")) {
29276
+ return path25.resolve(line.slice("worktree ".length));
29277
+ }
29278
+ }
29279
+ }
29280
+ return path25.resolve(toplevel);
29281
+ }
29282
+ function canonicalizeControlRepoPath(repoPath) {
29283
+ const resolved = path25.resolve(repoPath);
29284
+ const toplevel = tryGit(resolved, "rev-parse --show-toplevel");
29285
+ if (!toplevel) return resolved;
29286
+ const mainRoot = resolveMainWorkingTree(resolved);
29287
+ if (!mainRoot) return resolved;
29288
+ const top2 = path25.resolve(toplevel);
29289
+ const main = path25.resolve(mainRoot);
29290
+ if (top2 === main) return resolved;
29291
+ const rel = path25.relative(top2, resolved);
29292
+ if (!rel || rel === ".") return main;
29293
+ if (rel.startsWith(`..${path25.sep}`) || rel === ".." || path25.isAbsolute(rel)) {
29294
+ return resolved;
29295
+ }
29296
+ return path25.resolve(main, rel);
28854
29297
  }
28855
29298
 
28856
29299
  // src/core/control-registry.ts
28857
- var fs26 = __toESM(require("fs"));
28858
- var os4 = __toESM(require("os"));
28859
- var path25 = __toESM(require("path"));
28860
29300
  function getRegistryPath() {
28861
29301
  if (process.env.HAR_CONTROL_REGISTRY_PATH) {
28862
- return path25.resolve(process.env.HAR_CONTROL_REGISTRY_PATH);
29302
+ return path26.resolve(process.env.HAR_CONTROL_REGISTRY_PATH);
28863
29303
  }
28864
- return path25.join(os4.homedir(), ".har", "repos.json");
29304
+ return path26.join(os4.homedir(), ".har", "repos.json");
28865
29305
  }
28866
29306
  function readRegistry() {
28867
29307
  const registryPath = getRegistryPath();
28868
29308
  try {
28869
- if (!fs26.existsSync(registryPath)) return { repos: [] };
28870
- return JSON.parse(fs26.readFileSync(registryPath, "utf8"));
29309
+ if (!fs27.existsSync(registryPath)) return { repos: [] };
29310
+ return JSON.parse(fs27.readFileSync(registryPath, "utf8"));
28871
29311
  } catch {
28872
29312
  return { repos: [] };
28873
29313
  }
28874
29314
  }
28875
29315
  function writeRegistry(registry2) {
28876
29316
  const registryPath = getRegistryPath();
28877
- fs26.mkdirSync(path25.dirname(registryPath), { recursive: true });
28878
- fs26.writeFileSync(registryPath, JSON.stringify(registry2, null, 2) + "\n");
29317
+ fs27.mkdirSync(path26.dirname(registryPath), { recursive: true });
29318
+ fs27.writeFileSync(registryPath, JSON.stringify(registry2, null, 2) + "\n");
29319
+ }
29320
+ function registryChanged(before, after) {
29321
+ if (before.length !== after.length) return true;
29322
+ return before.some((repoPath, index) => repoPath !== after[index]);
28879
29323
  }
28880
29324
  function recordRepoForControlSync(repoPath) {
28881
29325
  if (process.env.HAR_CONTROL_DISABLED === "true") return;
28882
- const resolved = path25.resolve(repoPath);
29326
+ const resolved = canonicalizeControlRepoPath(repoPath);
28883
29327
  if (!readManifest(resolved)) return;
28884
29328
  const registry2 = readRegistry();
28885
29329
  if (registry2.repos.includes(resolved)) return;
@@ -28888,25 +29332,34 @@ function recordRepoForControlSync(repoPath) {
28888
29332
  }
28889
29333
  function listRegisteredRepos() {
28890
29334
  const registry2 = readRegistry();
28891
- const kept = registry2.repos.filter((repoPath) => fs26.existsSync(repoPath) && readManifest(repoPath));
28892
- if (kept.length !== registry2.repos.length) {
29335
+ const kept = [];
29336
+ const seen = /* @__PURE__ */ new Set();
29337
+ for (const repoPath of registry2.repos) {
29338
+ if (!fs27.existsSync(repoPath)) continue;
29339
+ const canonical = canonicalizeControlRepoPath(repoPath);
29340
+ if (!fs27.existsSync(canonical) || !readManifest(canonical)) continue;
29341
+ if (seen.has(canonical)) continue;
29342
+ seen.add(canonical);
29343
+ kept.push(canonical);
29344
+ }
29345
+ if (registryChanged(registry2.repos, kept)) {
28893
29346
  writeRegistry({ repos: kept });
28894
29347
  }
28895
29348
  return kept;
28896
29349
  }
28897
29350
 
28898
29351
  // src/core/slot-status.ts
28899
- var fs31 = __toESM(require("fs"));
29352
+ var fs32 = __toESM(require("fs"));
28900
29353
  var os7 = __toESM(require("os"));
28901
- var path31 = __toESM(require("path"));
29354
+ var path32 = __toESM(require("path"));
28902
29355
  var import_child_process7 = require("child_process");
28903
29356
 
28904
29357
  // src/core/runs.ts
28905
29358
  var crypto2 = __toESM(require("crypto"));
28906
29359
  var import_child_process2 = require("child_process");
28907
- var fs27 = __toESM(require("fs"));
29360
+ var fs28 = __toESM(require("fs"));
28908
29361
  var os5 = __toESM(require("os"));
28909
- var path26 = __toESM(require("path"));
29362
+ var path27 = __toESM(require("path"));
28910
29363
  var RUNS_DIR = "runs";
28911
29364
  function gitCommonDir(cwd) {
28912
29365
  try {
@@ -28915,7 +29368,7 @@ function gitCommonDir(cwd) {
28915
29368
  encoding: "utf8",
28916
29369
  stdio: ["pipe", "pipe", "ignore"]
28917
29370
  }).trim();
28918
- return out ? path26.resolve(cwd, out) : void 0;
29371
+ return out ? path27.resolve(cwd, out) : void 0;
28919
29372
  } catch {
28920
29373
  return void 0;
28921
29374
  }
@@ -28937,7 +29390,7 @@ function gitPrefix(cwd) {
28937
29390
  }
28938
29391
  }
28939
29392
  function getRunsDir(harnessRoot) {
28940
- return path26.join(getHarnessDir(harnessRoot), RUNS_DIR);
29393
+ return path27.join(getHarnessDir(harnessRoot), RUNS_DIR);
28941
29394
  }
28942
29395
  function formatLocalDate(d2) {
28943
29396
  const y2 = d2.getFullYear();
@@ -28957,24 +29410,24 @@ function buildRunRelativePath(stageId, agentId, startedAt, runId, runsDir) {
28957
29410
  const timePart = formatLocalTime(started);
28958
29411
  const agentPart = agentId !== void 0 ? `_agent-${agentId}` : "";
28959
29412
  let filename = `${timePart}_${stageId}${agentPart}.json`;
28960
- const fullPath = path26.join(runsDir, dateFolder, filename);
28961
- if (fs27.existsSync(fullPath)) {
29413
+ const fullPath = path27.join(runsDir, dateFolder, filename);
29414
+ if (fs28.existsSync(fullPath)) {
28962
29415
  filename = `${timePart}_${stageId}${agentPart}-${runId.slice(0, 8)}.json`;
28963
29416
  }
28964
- return path26.join(dateFolder, filename);
29417
+ return path27.join(dateFolder, filename);
28965
29418
  }
28966
29419
  function resolveRunFilePath(harnessRoot, run2) {
28967
29420
  if (run2.relativePath) {
28968
- return path26.join(getRunsDir(harnessRoot), run2.relativePath);
29421
+ return path27.join(getRunsDir(harnessRoot), run2.relativePath);
28969
29422
  }
28970
- return path26.join(getRunsDir(harnessRoot), `${run2.runId}.json`);
29423
+ return path27.join(getRunsDir(harnessRoot), `${run2.runId}.json`);
28971
29424
  }
28972
29425
  function collectRunFiles(runsDir) {
28973
- if (!fs27.existsSync(runsDir)) return [];
29426
+ if (!fs28.existsSync(runsDir)) return [];
28974
29427
  const files = [];
28975
29428
  const walk = (dir) => {
28976
- for (const entry of fs27.readdirSync(dir, { withFileTypes: true })) {
28977
- const full = path26.join(dir, entry.name);
29429
+ for (const entry of fs28.readdirSync(dir, { withFileTypes: true })) {
29430
+ const full = path27.join(dir, entry.name);
28978
29431
  if (entry.isDirectory()) {
28979
29432
  walk(full);
28980
29433
  } else if (entry.name.endsWith(".json")) {
@@ -28988,30 +29441,30 @@ function collectRunFiles(runsDir) {
28988
29441
  function resolveAgentWorkDir(harnessRoot, agentId) {
28989
29442
  if (agentId === void 0) return void 0;
28990
29443
  const entry = readSlotRegistry(harnessRoot, agentId);
28991
- if (entry?.workDir && fs27.existsSync(entry.workDir)) return entry.workDir;
29444
+ if (entry?.workDir && fs28.existsSync(entry.workDir)) return entry.workDir;
28992
29445
  const env3 = readHarnessEnv(harnessRoot);
28993
- const projectName = env3.HARNESS_PROJECT_NAME ?? path26.basename(harnessRoot);
28994
- const worktreeDir = path26.join(os5.homedir(), "worktrees", `${projectName}-agent-${agentId}`);
29446
+ const projectName = env3.HARNESS_PROJECT_NAME ?? path27.basename(harnessRoot);
29447
+ const worktreeDir = path27.join(os5.homedir(), "worktrees", `${projectName}-agent-${agentId}`);
28995
29448
  const relPrefix = gitPrefix(harnessRoot);
28996
29449
  const sessionEnvFiles = [];
28997
- const worktreesRoot = path26.join(os5.homedir(), "worktrees");
28998
- if (fs27.existsSync(worktreesRoot)) {
29450
+ const worktreesRoot = path27.join(os5.homedir(), "worktrees");
29451
+ if (fs28.existsSync(worktreesRoot)) {
28999
29452
  const suffix = `-har-agent-${agentId}-`;
29000
- for (const entry2 of fs27.readdirSync(worktreesRoot, { withFileTypes: true })) {
29453
+ for (const entry2 of fs28.readdirSync(worktreesRoot, { withFileTypes: true })) {
29001
29454
  if (!entry2.isDirectory() || !entry2.name.includes(suffix)) continue;
29002
- const sessionDir = path26.join(worktreesRoot, entry2.name);
29455
+ const sessionDir = path27.join(worktreesRoot, entry2.name);
29003
29456
  if (!sameGitCheckout(harnessRoot, sessionDir)) continue;
29004
- sessionEnvFiles.push(path26.join(sessionDir, relPrefix, `.env.agent.${agentId}`));
29457
+ sessionEnvFiles.push(path27.join(sessionDir, relPrefix, `.env.agent.${agentId}`));
29005
29458
  }
29006
29459
  }
29007
29460
  const candidates = [
29008
- path26.join(worktreeDir, `.env.agent.${agentId}`),
29009
- path26.join(harnessRoot, `.env.agent.${agentId}`),
29461
+ path27.join(worktreeDir, `.env.agent.${agentId}`),
29462
+ path27.join(harnessRoot, `.env.agent.${agentId}`),
29010
29463
  ...sessionEnvFiles.sort()
29011
29464
  ];
29012
29465
  for (const envFile of candidates) {
29013
- if (!fs27.existsSync(envFile)) continue;
29014
- const content = fs27.readFileSync(envFile, "utf8");
29466
+ if (!fs28.existsSync(envFile)) continue;
29467
+ const content = fs28.readFileSync(envFile, "utf8");
29015
29468
  const match = content.match(/^REPO_ROOT=(.+)$/m);
29016
29469
  if (match) return match[1].trim();
29017
29470
  }
@@ -29023,11 +29476,11 @@ function createRun(ctx, meta) {
29023
29476
  const startedAt = (/* @__PURE__ */ new Date()).toISOString();
29024
29477
  const runId = crypto2.randomUUID();
29025
29478
  const relativePath = buildRunRelativePath(meta.stageId, meta.agentId, startedAt, runId, runsDir);
29026
- const runFilePath = path26.join(runsDir, relativePath);
29027
- fs27.mkdirSync(path26.dirname(runFilePath), { recursive: true });
29479
+ const runFilePath = path27.join(runsDir, relativePath);
29480
+ fs28.mkdirSync(path27.dirname(runFilePath), { recursive: true });
29028
29481
  const run2 = RunRecordSchema.parse({
29029
29482
  runId,
29030
- repoPath: path26.resolve(ctx.repoPath),
29483
+ repoPath: path27.resolve(ctx.repoPath),
29031
29484
  harnessRoot,
29032
29485
  stageId: meta.stageId,
29033
29486
  kind: meta.kind,
@@ -29038,7 +29491,7 @@ function createRun(ctx, meta) {
29038
29491
  relativePath,
29039
29492
  trigger: ctx.trigger ?? "cli"
29040
29493
  });
29041
- fs27.writeFileSync(runFilePath, JSON.stringify(run2, null, 2) + "\n");
29494
+ fs28.writeFileSync(runFilePath, JSON.stringify(run2, null, 2) + "\n");
29042
29495
  return run2;
29043
29496
  }
29044
29497
  function finishRun(repoPath, runId, update) {
@@ -29057,13 +29510,13 @@ function finishRun(repoPath, runId, update) {
29057
29510
  finishedAt: (/* @__PURE__ */ new Date()).toISOString()
29058
29511
  });
29059
29512
  const runPath = resolveRunFilePath(harnessRoot, finished);
29060
- fs27.writeFileSync(runPath, JSON.stringify(finished, null, 2) + "\n");
29513
+ fs28.writeFileSync(runPath, JSON.stringify(finished, null, 2) + "\n");
29061
29514
  return finished;
29062
29515
  }
29063
29516
  function findRunRecord(harnessRoot, runId) {
29064
29517
  const runsDir = getRunsDir(harnessRoot);
29065
29518
  for (const filePath of collectRunFiles(runsDir)) {
29066
- const parsed = RunRecordSchema.safeParse(JSON.parse(fs27.readFileSync(filePath, "utf8")));
29519
+ const parsed = RunRecordSchema.safeParse(JSON.parse(fs28.readFileSync(filePath, "utf8")));
29067
29520
  if (parsed.success && parsed.data.runId === runId) {
29068
29521
  return parsed.data;
29069
29522
  }
@@ -29075,10 +29528,10 @@ function getRun(repoPath, runId) {
29075
29528
  }
29076
29529
  function listRuns(repoPath, filter = {}) {
29077
29530
  const runsDir = getRunsDir(resolveHarnessRoot(repoPath));
29078
- if (!fs27.existsSync(runsDir)) return [];
29531
+ if (!fs28.existsSync(runsDir)) return [];
29079
29532
  const runs = [];
29080
29533
  for (const filePath of collectRunFiles(runsDir)) {
29081
- const parsed = RunRecordSchema.safeParse(JSON.parse(fs27.readFileSync(filePath, "utf8")));
29534
+ const parsed = RunRecordSchema.safeParse(JSON.parse(fs28.readFileSync(filePath, "utf8")));
29082
29535
  if (!parsed.success) continue;
29083
29536
  if (filter.stageId && parsed.data.stageId !== filter.stageId) continue;
29084
29537
  runs.push(parsed.data);
@@ -29091,17 +29544,18 @@ function listRuns(repoPath, filter = {}) {
29091
29544
  }
29092
29545
 
29093
29546
  // src/core/slot-preflight.ts
29094
- var path30 = __toESM(require("path"));
29547
+ var path31 = __toESM(require("path"));
29095
29548
 
29096
29549
  // src/core/control-port.ts
29097
29550
  var import_child_process4 = require("child_process");
29098
- var fs29 = __toESM(require("fs"));
29099
- var path28 = __toESM(require("path"));
29551
+ var fs30 = __toESM(require("fs"));
29552
+ var path29 = __toESM(require("path"));
29553
+ init_control_config();
29100
29554
 
29101
29555
  // src/core/slot-ports.ts
29102
29556
  var import_child_process3 = require("child_process");
29103
- var fs28 = __toESM(require("fs"));
29104
- var path27 = __toESM(require("path"));
29557
+ var fs29 = __toESM(require("fs"));
29558
+ var path28 = __toESM(require("path"));
29105
29559
  function portStep(env3) {
29106
29560
  return Number(env3.HARNESS_PORT_STEP ?? 10);
29107
29561
  }
@@ -29157,7 +29611,7 @@ function allocateAppPorts(repoPath, agentId) {
29157
29611
  }
29158
29612
  function harnessUsesPm2(repoPath) {
29159
29613
  const harnessRoot = resolveHarnessRoot(repoPath);
29160
- return fs28.existsSync(path27.join(harnessRoot, ".har", "ecosystem.agent.template.cjs"));
29614
+ return fs29.existsSync(path28.join(harnessRoot, ".har", "ecosystem.agent.template.cjs"));
29161
29615
  }
29162
29616
 
29163
29617
  // src/core/control-port.ts
@@ -29212,13 +29666,13 @@ function controlDefaultPortWarnings(containers, defaultFrontendPort, allocatedFr
29212
29666
  return [formatControlDefaultPortWarning(control.name, defaultFrontendPort, allocatedFrontend)];
29213
29667
  }
29214
29668
  function inspectControlUpReadiness(repoPath) {
29215
- const resolved = path28.resolve(repoPath);
29669
+ const resolved = path29.resolve(repoPath);
29216
29670
  const port = parseControlHostPort();
29217
29671
  const containers = listDockerContainers();
29218
29672
  const control = controlContainerOnPort(containers, port);
29219
29673
  const warnings = [];
29220
- const controlHarnessDir = path28.join(resolved, "control", ".har");
29221
- const harnessSlot1Active = fs29.existsSync(controlHarnessDir) && readSlotRegistry(path28.join(resolved, "control"), 1)?.status === "active";
29674
+ const controlHarnessDir = path29.join(resolved, "control", ".har");
29675
+ const harnessSlot1Active = fs30.existsSync(controlHarnessDir) && readSlotRegistry(path29.join(resolved, "control"), 1)?.status === "active";
29222
29676
  if (control) {
29223
29677
  return {
29224
29678
  warnings,
@@ -29249,9 +29703,9 @@ function inspectControlUpReadiness(repoPath) {
29249
29703
  }
29250
29704
 
29251
29705
  // src/core/slot-launch-guard-occupied.ts
29252
- var fs30 = __toESM(require("fs"));
29706
+ var fs31 = __toESM(require("fs"));
29253
29707
  var os6 = __toESM(require("os"));
29254
- var path29 = __toESM(require("path"));
29708
+ var path30 = __toESM(require("path"));
29255
29709
  var import_child_process5 = require("child_process");
29256
29710
  function runGit(cwd, args) {
29257
29711
  try {
@@ -29270,7 +29724,7 @@ function worktreeDirty(worktreePath) {
29270
29724
  }
29271
29725
  function gitCommonDir2(cwd) {
29272
29726
  const out = runGit(cwd, "rev-parse --git-common-dir");
29273
- return out ? path29.resolve(cwd, out) : void 0;
29727
+ return out ? path30.resolve(cwd, out) : void 0;
29274
29728
  }
29275
29729
  function sameGitCheckout2(a2, b2) {
29276
29730
  const left2 = gitCommonDir2(a2);
@@ -29279,20 +29733,20 @@ function sameGitCheckout2(a2, b2) {
29279
29733
  }
29280
29734
  function discoverWorktree(harnessRoot, agentId, projectName) {
29281
29735
  const session = readSlotRegistry(harnessRoot, agentId);
29282
- if (session?.worktreePath && fs30.existsSync(session.worktreePath)) {
29736
+ if (session?.worktreePath && fs31.existsSync(session.worktreePath)) {
29283
29737
  return session.worktreePath;
29284
29738
  }
29285
- const legacy = path29.join(os6.homedir(), "worktrees", `${projectName}-agent-${agentId}`);
29286
- if (fs30.existsSync(legacy) && sameGitCheckout2(harnessRoot, legacy)) return legacy;
29287
- const worktreesRoot = path29.join(os6.homedir(), "worktrees");
29288
- if (!fs30.existsSync(worktreesRoot)) return void 0;
29739
+ const legacy = path30.join(os6.homedir(), "worktrees", `${projectName}-agent-${agentId}`);
29740
+ if (fs31.existsSync(legacy) && sameGitCheckout2(harnessRoot, legacy)) return legacy;
29741
+ const worktreesRoot = path30.join(os6.homedir(), "worktrees");
29742
+ if (!fs31.existsSync(worktreesRoot)) return void 0;
29289
29743
  const suffix = `-har-agent-${agentId}-`;
29290
29744
  const relPrefix = runGit(harnessRoot, "rev-parse --show-prefix") ?? "";
29291
- for (const name of fs30.readdirSync(worktreesRoot)) {
29745
+ for (const name of fs31.readdirSync(worktreesRoot)) {
29292
29746
  if (!name.includes(suffix)) continue;
29293
- const candidate = path29.join(worktreesRoot, name);
29747
+ const candidate = path30.join(worktreesRoot, name);
29294
29748
  if (!sameGitCheckout2(harnessRoot, candidate)) continue;
29295
- if (!fs30.existsSync(path29.join(candidate, relPrefix, `.env.agent.${agentId}`))) continue;
29749
+ if (!fs31.existsSync(path30.join(candidate, relPrefix, `.env.agent.${agentId}`))) continue;
29296
29750
  return candidate;
29297
29751
  }
29298
29752
  return void 0;
@@ -29300,13 +29754,13 @@ function discoverWorktree(harnessRoot, agentId, projectName) {
29300
29754
  function collectOccupiedSlot(repoPath, agentId) {
29301
29755
  const harnessRoot = resolveHarnessRoot(repoPath);
29302
29756
  const env3 = readHarnessEnv(harnessRoot);
29303
- const projectName = env3.HARNESS_PROJECT_NAME ?? path29.basename(harnessRoot);
29757
+ const projectName = env3.HARNESS_PROJECT_NAME ?? path30.basename(harnessRoot);
29304
29758
  const session = readSlotRegistry(harnessRoot, agentId);
29305
29759
  const worktreePath = discoverWorktree(harnessRoot, agentId, projectName);
29306
29760
  const workDir = session?.workDir;
29307
- const envInWorkDir = workDir ? fs30.existsSync(path29.join(workDir, `.env.agent.${agentId}`)) : false;
29308
- const envInRoot = fs30.existsSync(path29.join(harnessRoot, `.env.agent.${agentId}`));
29309
- const envInWorktree = worktreePath ? fs30.existsSync(path29.join(worktreePath, `.env.agent.${agentId}`)) : false;
29761
+ const envInWorkDir = workDir ? fs31.existsSync(path30.join(workDir, `.env.agent.${agentId}`)) : false;
29762
+ const envInRoot = fs31.existsSync(path30.join(harnessRoot, `.env.agent.${agentId}`));
29763
+ const envInWorktree = worktreePath ? fs31.existsSync(path30.join(worktreePath, `.env.agent.${agentId}`)) : false;
29310
29764
  const active = session !== void 0 && session.status !== "completed" || envInWorkDir || envInRoot || envInWorktree;
29311
29765
  if (!active) return void 0;
29312
29766
  const dirty = worktreePath ? worktreeDirty(worktreePath) : void 0;
@@ -29319,7 +29773,6 @@ function collectOccupiedSlot(repoPath, agentId) {
29319
29773
  sessionStatus: session?.status,
29320
29774
  lastError: session?.lastError,
29321
29775
  sessionCreatedAt: session?.createdAt,
29322
- purpose: session?.purpose,
29323
29776
  dirty,
29324
29777
  harnessUsage: "none"
29325
29778
  };
@@ -29330,7 +29783,6 @@ function formatOccupiedSlot(slot, resume) {
29330
29783
  slot.worktreePath ? ` Worktree: ${slot.worktreePath}` : void 0,
29331
29784
  slot.branch ? ` Branch: ${slot.branch}` : void 0,
29332
29785
  slot.workDir ? ` Work dir: ${slot.workDir}` : void 0,
29333
- slot.purpose ? ` Purpose: ${slot.purpose}` : void 0,
29334
29786
  slot.sessionStatus ? ` Status: ${slot.sessionStatus}` : void 0,
29335
29787
  slot.lastError ? ` Error: ${slot.lastError}` : void 0,
29336
29788
  slot.sessionCreatedAt ? ` Since: ${slot.sessionCreatedAt}` : void 0,
@@ -29461,7 +29913,7 @@ function checkInfraPort(env3, varName, defaultPort, scanStart, scanEnd) {
29461
29913
  function inspectSlotReadiness(repoPath, agentId, options = {}) {
29462
29914
  const harnessRoot = resolveHarnessRoot(repoPath);
29463
29915
  const env3 = readHarnessEnv(harnessRoot);
29464
- const projectName = env3.HARNESS_PROJECT_NAME ?? path30.basename(harnessRoot);
29916
+ const projectName = env3.HARNESS_PROJECT_NAME ?? path31.basename(harnessRoot);
29465
29917
  const usesPm2 = harnessUsesPm2(repoPath);
29466
29918
  const blockers = [];
29467
29919
  const remediations = [];
@@ -29663,7 +30115,7 @@ function readWorktreeBranch(worktreePath) {
29663
30115
  }
29664
30116
  function gitCommonDir3(cwd) {
29665
30117
  const out = runGit2(cwd, "rev-parse --git-common-dir");
29666
- return out ? path31.resolve(cwd, out) : void 0;
30118
+ return out ? path32.resolve(cwd, out) : void 0;
29667
30119
  }
29668
30120
  function sameGitCheckout3(a2, b2) {
29669
30121
  const left2 = gitCommonDir3(a2);
@@ -29675,12 +30127,12 @@ function gitPrefix2(cwd) {
29675
30127
  return out ?? "";
29676
30128
  }
29677
30129
  function discoverSessionWorktreePath(harnessRoot, agentId) {
29678
- const worktreesRoot = path31.join(os7.homedir(), "worktrees");
29679
- if (!fs31.existsSync(worktreesRoot)) return void 0;
30130
+ const worktreesRoot = path32.join(os7.homedir(), "worktrees");
30131
+ if (!fs32.existsSync(worktreesRoot)) return void 0;
29680
30132
  const suffix = `-har-agent-${agentId}-`;
29681
30133
  const relPrefix = gitPrefix2(harnessRoot);
29682
- const matches = fs31.readdirSync(worktreesRoot, { withFileTypes: true }).filter((entry) => entry.isDirectory() && entry.name.includes(suffix)).map((entry) => path31.join(worktreesRoot, entry.name)).filter(
29683
- (candidate) => sameGitCheckout3(harnessRoot, candidate) && fs31.existsSync(path31.join(candidate, relPrefix, `.env.agent.${agentId}`))
30134
+ const matches = fs32.readdirSync(worktreesRoot, { withFileTypes: true }).filter((entry) => entry.isDirectory() && entry.name.includes(suffix)).map((entry) => path32.join(worktreesRoot, entry.name)).filter(
30135
+ (candidate) => sameGitCheckout3(harnessRoot, candidate) && fs32.existsSync(path32.join(candidate, relPrefix, `.env.agent.${agentId}`))
29684
30136
  );
29685
30137
  return matches.sort()[0];
29686
30138
  }
@@ -29755,18 +30207,18 @@ function detectPm2Issue(projectName, agentId, session, procs) {
29755
30207
  }
29756
30208
  function collectSlotStatus(harnessRoot, agentId, runs, pm2Procs) {
29757
30209
  const env3 = readHarnessEnv(harnessRoot);
29758
- const projectName = env3.HARNESS_PROJECT_NAME ?? path31.basename(harnessRoot);
30210
+ const projectName = env3.HARNESS_PROJECT_NAME ?? path32.basename(harnessRoot);
29759
30211
  const session = readSlotRegistry(harnessRoot, agentId);
29760
- const legacyWorktreePath = path31.join(
30212
+ const legacyWorktreePath = path32.join(
29761
30213
  os7.homedir(),
29762
30214
  "worktrees",
29763
30215
  `${projectName}-agent-${agentId}`
29764
30216
  );
29765
- const worktreePath = session?.worktreePath && fs31.existsSync(session.worktreePath) ? session.worktreePath : fs31.existsSync(legacyWorktreePath) ? legacyWorktreePath : discoverSessionWorktreePath(harnessRoot, agentId);
30217
+ const worktreePath = session?.worktreePath && fs32.existsSync(session.worktreePath) ? session.worktreePath : fs32.existsSync(legacyWorktreePath) ? legacyWorktreePath : discoverSessionWorktreePath(harnessRoot, agentId);
29766
30218
  const workDir = resolveAgentWorkDir(harnessRoot, agentId);
29767
- const envInWorkDir = workDir ? fs31.existsSync(path31.join(workDir, `.env.agent.${agentId}`)) : false;
29768
- const envInRoot = fs31.existsSync(path31.join(harnessRoot, `.env.agent.${agentId}`));
29769
- const envInWorktree = worktreePath ? fs31.existsSync(path31.join(worktreePath, `.env.agent.${agentId}`)) : false;
30219
+ const envInWorkDir = workDir ? fs32.existsSync(path32.join(workDir, `.env.agent.${agentId}`)) : false;
30220
+ const envInRoot = fs32.existsSync(path32.join(harnessRoot, `.env.agent.${agentId}`));
30221
+ const envInWorktree = worktreePath ? fs32.existsSync(path32.join(worktreePath, `.env.agent.${agentId}`)) : false;
29770
30222
  const active = session !== void 0 && session.status !== "completed" || envInWorkDir || envInRoot || envInWorktree;
29771
30223
  const latest = latestRunForAgent(runs, agentId);
29772
30224
  const verifyRun = runs.find((r2) => r2.agentId === agentId && r2.stageId === "verify");
@@ -29805,7 +30257,6 @@ function collectSlotStatus(harnessRoot, agentId, runs, pm2Procs) {
29805
30257
  baseBranch: session?.baseBranch,
29806
30258
  baseCommit: session?.baseCommit,
29807
30259
  sessionCreatedAt: session?.createdAt,
29808
- purpose: session?.purpose,
29809
30260
  sessionStatus: session?.status,
29810
30261
  lastError: session?.lastError,
29811
30262
  resumeHint,
@@ -29819,7 +30270,7 @@ function collectEnvironmentStatus(repoPath) {
29819
30270
  const slotIds = getAgentSlotIds(harnessRoot);
29820
30271
  const pm2Procs = listPm2Processes2();
29821
30272
  return {
29822
- repoPath: path31.resolve(repoPath),
30273
+ repoPath: path32.resolve(repoPath),
29823
30274
  harnessRoot,
29824
30275
  gitRemote: readGitRemote(harnessRoot),
29825
30276
  profile: manifest?.profile,
@@ -29830,13 +30281,13 @@ function collectEnvironmentStatus(repoPath) {
29830
30281
 
29831
30282
  // src/core/validations.ts
29832
30283
  var crypto3 = __toESM(require("crypto"));
29833
- var fs33 = __toESM(require("fs"));
29834
- var path33 = __toESM(require("path"));
30284
+ var fs34 = __toESM(require("fs"));
30285
+ var path34 = __toESM(require("path"));
29835
30286
 
29836
30287
  // src/core/change-batch.ts
29837
- var fs32 = __toESM(require("fs"));
30288
+ var fs33 = __toESM(require("fs"));
29838
30289
  var os8 = __toESM(require("os"));
29839
- var path32 = __toESM(require("path"));
30290
+ var path33 = __toESM(require("path"));
29840
30291
  function git(checkoutDir, args, env3) {
29841
30292
  const result = run(`git ${args}`, { cwd: checkoutDir, env: env3 });
29842
30293
  if (result.code !== 0) {
@@ -29844,37 +30295,37 @@ function git(checkoutDir, args, env3) {
29844
30295
  }
29845
30296
  return result.stdout.trim();
29846
30297
  }
29847
- function tryGit(checkoutDir, args) {
30298
+ function tryGit2(checkoutDir, args) {
29848
30299
  const result = run(`git ${args}`, { cwd: checkoutDir });
29849
30300
  return result.code === 0 ? result.stdout.trim() : void 0;
29850
30301
  }
29851
30302
  function isCheckoutRoot(checkoutDir) {
29852
- const toplevel = tryGit(checkoutDir, "rev-parse --show-toplevel");
30303
+ const toplevel = tryGit2(checkoutDir, "rev-parse --show-toplevel");
29853
30304
  if (!toplevel) return false;
29854
30305
  try {
29855
- return fs32.realpathSync(toplevel) === fs32.realpathSync(checkoutDir);
30306
+ return fs33.realpathSync(toplevel) === fs33.realpathSync(checkoutDir);
29856
30307
  } catch {
29857
30308
  return false;
29858
30309
  }
29859
30310
  }
29860
30311
  function getHeadTree(checkoutDir) {
29861
- return tryGit(checkoutDir, "rev-parse -q --verify HEAD^{tree}");
30312
+ return tryGit2(checkoutDir, "rev-parse -q --verify HEAD^{tree}");
29862
30313
  }
29863
30314
  function getHeadSha(checkoutDir) {
29864
- return tryGit(checkoutDir, "rev-parse -q --verify HEAD");
30315
+ return tryGit2(checkoutDir, "rev-parse -q --verify HEAD");
29865
30316
  }
29866
30317
  function getCurrentBranch(checkoutDir) {
29867
- const branch = tryGit(checkoutDir, "rev-parse --abbrev-ref HEAD");
30318
+ const branch = tryGit2(checkoutDir, "rev-parse --abbrev-ref HEAD");
29868
30319
  return branch && branch !== "HEAD" ? branch : void 0;
29869
30320
  }
29870
30321
  function getEmptyTree(checkoutDir) {
29871
30322
  return git(checkoutDir, "hash-object -t tree /dev/null");
29872
30323
  }
29873
30324
  function isMergeOrRebaseInProgress(checkoutDir) {
29874
- if (tryGit(checkoutDir, "rev-parse -q --verify MERGE_HEAD") !== void 0) return true;
30325
+ if (tryGit2(checkoutDir, "rev-parse -q --verify MERGE_HEAD") !== void 0) return true;
29875
30326
  for (const dir of ["rebase-merge", "rebase-apply"]) {
29876
- const gitPath = tryGit(checkoutDir, `rev-parse --git-path ${dir}`);
29877
- if (gitPath && fs32.existsSync(path32.resolve(checkoutDir, gitPath))) return true;
30327
+ const gitPath = tryGit2(checkoutDir, `rev-parse --git-path ${dir}`);
30328
+ if (gitPath && fs33.existsSync(path33.resolve(checkoutDir, gitPath))) return true;
29878
30329
  }
29879
30330
  return false;
29880
30331
  }
@@ -29897,8 +30348,8 @@ function parseNameStatus(output) {
29897
30348
  return files;
29898
30349
  }
29899
30350
  function computeWorktreeSnapshot(checkoutDir) {
29900
- const tmpDir = fs32.mkdtempSync(path32.join(os8.tmpdir(), "har-idx-"));
29901
- const tmpIndex = path32.join(tmpDir, "index");
30351
+ const tmpDir = fs33.mkdtempSync(path33.join(os8.tmpdir(), "har-idx-"));
30352
+ const tmpIndex = path33.join(tmpDir, "index");
29902
30353
  const env3 = { GIT_INDEX_FILE: tmpIndex };
29903
30354
  try {
29904
30355
  const headTree = getHeadTree(checkoutDir);
@@ -29921,35 +30372,35 @@ function computeWorktreeSnapshot(checkoutDir) {
29921
30372
  changedFiles: parseNameStatus(diffOutput)
29922
30373
  };
29923
30374
  } finally {
29924
- fs32.rmSync(tmpDir, { recursive: true, force: true });
30375
+ fs33.rmSync(tmpDir, { recursive: true, force: true });
29925
30376
  }
29926
30377
  }
29927
30378
 
29928
30379
  // src/core/validations.ts
29929
30380
  var VALIDATIONS_DIR = "validations";
29930
30381
  function getValidationsDir(checkoutDir) {
29931
- return path33.join(checkoutDir, ".har", VALIDATIONS_DIR);
30382
+ return path34.join(checkoutDir, ".har", VALIDATIONS_DIR);
29932
30383
  }
29933
30384
  function validationPath(checkoutDir, treeHash) {
29934
- return path33.join(getValidationsDir(checkoutDir), `${treeHash}.json`);
30385
+ return path34.join(getValidationsDir(checkoutDir), `${treeHash}.json`);
29935
30386
  }
29936
30387
  function writeRecord(checkoutDir, record2) {
29937
30388
  const dir = getValidationsDir(checkoutDir);
29938
- fs33.mkdirSync(dir, { recursive: true });
29939
- fs33.writeFileSync(
30389
+ fs34.mkdirSync(dir, { recursive: true });
30390
+ fs34.writeFileSync(
29940
30391
  validationPath(checkoutDir, record2.treeHash),
29941
30392
  `${JSON.stringify(record2, null, 2)}
29942
30393
  `
29943
30394
  );
29944
30395
  }
29945
30396
  function ensureValidationsIgnored(checkoutDir) {
29946
- const harDir = path33.join(checkoutDir, ".har");
29947
- if (!fs33.existsSync(harDir)) return;
29948
- const gitignorePath = path33.join(harDir, ".gitignore");
29949
- const content = fs33.existsSync(gitignorePath) ? fs33.readFileSync(gitignorePath, "utf8") : "";
30397
+ const harDir = path34.join(checkoutDir, ".har");
30398
+ if (!fs34.existsSync(harDir)) return;
30399
+ const gitignorePath = path34.join(harDir, ".gitignore");
30400
+ const content = fs34.existsSync(gitignorePath) ? fs34.readFileSync(gitignorePath, "utf8") : "";
29950
30401
  if (content.split("\n").some((line) => line.trim() === `${VALIDATIONS_DIR}/`)) return;
29951
30402
  const suffix = content.length === 0 || content.endsWith("\n") ? "" : "\n";
29952
- fs33.writeFileSync(gitignorePath, `${content}${suffix}${VALIDATIONS_DIR}/
30403
+ fs34.writeFileSync(gitignorePath, `${content}${suffix}${VALIDATIONS_DIR}/
29953
30404
  `);
29954
30405
  }
29955
30406
  function recordValidation(input) {
@@ -29966,8 +30417,8 @@ function recordValidation(input) {
29966
30417
  treeHash: snapshot.treeHash,
29967
30418
  headSha: snapshot.headSha,
29968
30419
  branch: snapshot.branch,
29969
- workDir: path33.resolve(input.checkoutDir),
29970
- harnessRoot: path33.resolve(input.harnessRoot),
30420
+ workDir: path34.resolve(input.checkoutDir),
30421
+ harnessRoot: path34.resolve(input.harnessRoot),
29971
30422
  agentId: input.agentId,
29972
30423
  status: input.status,
29973
30424
  full: input.full,
@@ -29979,16 +30430,16 @@ function recordValidation(input) {
29979
30430
  committedAt: existing?.committedAt
29980
30431
  });
29981
30432
  writeRecord(input.checkoutDir, record2);
29982
- if (path33.resolve(input.harnessRoot) !== path33.resolve(input.checkoutDir)) {
30433
+ if (path34.resolve(input.harnessRoot) !== path34.resolve(input.checkoutDir)) {
29983
30434
  writeRecord(input.harnessRoot, record2);
29984
30435
  }
29985
30436
  return record2;
29986
30437
  }
29987
30438
  function findValidation(checkoutDir, treeHash) {
29988
30439
  const file = validationPath(checkoutDir, treeHash);
29989
- if (!fs33.existsSync(file)) return void 0;
30440
+ if (!fs34.existsSync(file)) return void 0;
29990
30441
  try {
29991
- return ValidationRecordSchema.parse(JSON.parse(fs33.readFileSync(file, "utf8")));
30442
+ return ValidationRecordSchema.parse(JSON.parse(fs34.readFileSync(file, "utf8")));
29992
30443
  } catch {
29993
30444
  return void 0;
29994
30445
  }
@@ -30003,19 +30454,19 @@ function attachCommit(checkoutDir, treeHash, commitSha) {
30003
30454
  updatedAt: (/* @__PURE__ */ new Date()).toISOString()
30004
30455
  };
30005
30456
  writeRecord(checkoutDir, updated);
30006
- if (record2.harnessRoot && path33.resolve(record2.harnessRoot) !== path33.resolve(checkoutDir) && fs33.existsSync(path33.join(record2.harnessRoot, ".har"))) {
30457
+ if (record2.harnessRoot && path34.resolve(record2.harnessRoot) !== path34.resolve(checkoutDir) && fs34.existsSync(path34.join(record2.harnessRoot, ".har"))) {
30007
30458
  writeRecord(record2.harnessRoot, updated);
30008
30459
  }
30009
30460
  return updated;
30010
30461
  }
30011
30462
  function listValidations(harnessRoot) {
30012
30463
  const dir = getValidationsDir(harnessRoot);
30013
- if (!fs33.existsSync(dir)) return [];
30464
+ if (!fs34.existsSync(dir)) return [];
30014
30465
  const records = [];
30015
- for (const entry of fs33.readdirSync(dir)) {
30466
+ for (const entry of fs34.readdirSync(dir)) {
30016
30467
  if (!entry.endsWith(".json")) continue;
30017
30468
  try {
30018
- records.push(ValidationRecordSchema.parse(JSON.parse(fs33.readFileSync(path33.join(dir, entry), "utf8"))));
30469
+ records.push(ValidationRecordSchema.parse(JSON.parse(fs34.readFileSync(path34.join(dir, entry), "utf8"))));
30019
30470
  } catch {
30020
30471
  }
30021
30472
  }
@@ -30044,105 +30495,19 @@ function createRemoteExecutor(apiUrl, apiKey) {
30044
30495
  return new RemoteExecutor(url, key);
30045
30496
  }
30046
30497
 
30047
- // src/core/telemetry-config.ts
30048
- var fs34 = __toESM(require("fs"));
30049
- var os9 = __toESM(require("os"));
30050
- var path34 = __toESM(require("path"));
30051
- var DEFAULT_SIGNALS_ON = {
30052
- metrics: true,
30053
- logs: true,
30054
- prompts: false,
30055
- traces: false
30056
- };
30057
- var DEFAULT_SIGNALS_OFF = {
30058
- metrics: false,
30059
- logs: false,
30060
- prompts: false,
30061
- traces: false
30062
- };
30063
- function getPreferencePath() {
30064
- if (process.env.HAR_TELEMETRY_CONFIG_PATH) {
30065
- return path34.resolve(process.env.HAR_TELEMETRY_CONFIG_PATH);
30066
- }
30067
- return path34.join(os9.homedir(), ".har", "telemetry.json");
30068
- }
30069
- function parseEnvOverride(raw) {
30070
- if (raw === void 0 || raw === "") return void 0;
30071
- const normalized = raw.trim().toLowerCase();
30072
- if (["0", "false", "off", "no", "disabled"].includes(normalized)) return false;
30073
- if (["1", "true", "on", "yes", "enabled"].includes(normalized)) return true;
30074
- return void 0;
30075
- }
30076
- function normalizeSignals(enabled, raw) {
30077
- if (!enabled) return { ...DEFAULT_SIGNALS_OFF };
30078
- return {
30079
- metrics: raw?.metrics !== false,
30080
- logs: raw?.logs !== false,
30081
- prompts: raw?.prompts === true,
30082
- traces: raw?.traces === true
30083
- };
30084
- }
30085
- function readTelemetryPreference() {
30086
- const preferencePath = getPreferencePath();
30087
- try {
30088
- if (!fs34.existsSync(preferencePath)) {
30089
- return { enabled: true, signals: { ...DEFAULT_SIGNALS_ON } };
30090
- }
30091
- const parsed = JSON.parse(fs34.readFileSync(preferencePath, "utf8"));
30092
- const enabled = parsed.enabled !== false;
30093
- return {
30094
- enabled,
30095
- signals: normalizeSignals(enabled, parsed.signals),
30096
- updatedAt: typeof parsed.updatedAt === "string" ? parsed.updatedAt : void 0
30097
- };
30098
- } catch {
30099
- return { enabled: true, signals: { ...DEFAULT_SIGNALS_ON } };
30100
- }
30101
- }
30102
- function writeTelemetryPreference(enabled, signals) {
30103
- const current = readTelemetryPreference();
30104
- const nextSignals = normalizeSignals(enabled, {
30105
- ...current.signals,
30106
- ...signals
30107
- });
30108
- const preference = {
30109
- enabled,
30110
- signals: enabled ? nextSignals : { ...DEFAULT_SIGNALS_OFF },
30111
- updatedAt: (/* @__PURE__ */ new Date()).toISOString()
30112
- };
30113
- const preferencePath = getPreferencePath();
30114
- fs34.mkdirSync(path34.dirname(preferencePath), { recursive: true });
30115
- fs34.writeFileSync(preferencePath, JSON.stringify(preference, null, 2) + "\n");
30116
- return preference;
30117
- }
30118
- function isTelemetryEnabled() {
30119
- const envOverride = parseEnvOverride(process.env.HAR_TELEMETRY);
30120
- if (envOverride !== void 0) return envOverride;
30121
- return readTelemetryPreference().enabled;
30122
- }
30123
- function getTelemetrySignals() {
30124
- if (!isTelemetryEnabled()) return { ...DEFAULT_SIGNALS_OFF };
30125
- return readTelemetryPreference().signals;
30126
- }
30127
- function getTelemetryPreferencePath() {
30128
- return getPreferencePath();
30129
- }
30130
- var TELEMETRY_SIGNALS = [
30131
- "Metrics (default): tokens and estimated USD cost via OTEL metrics",
30132
- "Logs/events (default): session events (tool calls, api_request) without prompt bodies",
30133
- "Prompts (opt-in): user/assistant text via OTEL_LOG_USER_PROMPTS \u2014 har telemetry on --prompts",
30134
- "Traces (opt-in): thin span ingest when CLAUDE_CODE_ENHANCED_TELEMETRY_BETA=1",
30135
- "Fallback: har control sync harvests local Claude/Codex session files when OTEL is missing"
30136
- ];
30498
+ // src/core/control-sync.ts
30499
+ init_telemetry_config();
30137
30500
 
30138
30501
  // src/core/usage-harvest/claude.ts
30139
- var fs36 = __toESM(require("fs"));
30502
+ var fs37 = __toESM(require("fs"));
30140
30503
  var os10 = __toESM(require("os"));
30141
- var path36 = __toESM(require("path"));
30504
+ var path37 = __toESM(require("path"));
30505
+ init_telemetry_config();
30142
30506
 
30143
30507
  // src/core/telemetry-env.ts
30144
- var fs35 = __toESM(require("fs"));
30145
- var path35 = __toESM(require("path"));
30508
+ var fs36 = __toESM(require("fs"));
30509
+ var path36 = __toESM(require("path"));
30510
+ init_control_config();
30146
30511
  function escapeAttrValue(value) {
30147
30512
  return value.replace(/[,=]/g, "_");
30148
30513
  }
@@ -30155,7 +30520,6 @@ function buildOtelResourceAttributes(attrs) {
30155
30520
  ];
30156
30521
  if (attrs.branch) parts.push(`har.branch=${escapeAttrValue(attrs.branch)}`);
30157
30522
  if (attrs.suffix) parts.push(`har.suffix=${escapeAttrValue(attrs.suffix)}`);
30158
- if (attrs.purpose) parts.push(`har.purpose=${escapeAttrValue(attrs.purpose)}`);
30159
30523
  return parts.join(",");
30160
30524
  }
30161
30525
  function buildSessionKey(input) {
@@ -30163,54 +30527,27 @@ function buildSessionKey(input) {
30163
30527
  const stamp = (input.createdAt ?? (/* @__PURE__ */ new Date()).toISOString()).replace(/[:.]/g, "-");
30164
30528
  return `agent-${input.agentId}-${input.suffix ?? stamp}`;
30165
30529
  }
30166
- function buildTelemetryEnvBlock(attrs, options) {
30530
+ function buildTelemetryEnvBlock(attrs) {
30167
30531
  const apiUrl = getControlApiUrl().replace(/\/$/, "");
30168
30532
  const lines = [
30169
30533
  "",
30170
30534
  "# HAR session attribution (generated)",
30171
30535
  `HAR_SESSION_KEY=${attrs.sessionKey}`,
30172
30536
  `HAR_CONTROL_API_URL=${apiUrl}`,
30173
- `OTEL_RESOURCE_ATTRIBUTES=${buildOtelResourceAttributes(attrs)}`
30537
+ `OTEL_RESOURCE_ATTRIBUTES=${buildOtelResourceAttributes(attrs)}`,
30538
+ "# Agent telemetry: opentelemetry-hooks \u2192 Mission Control (har telemetry on|off)"
30174
30539
  ];
30175
- const injectOtel = isTelemetryEnabled() && options?.otelReady !== false;
30176
- if (injectOtel) {
30177
- const signals = getTelemetrySignals();
30178
- lines.push(
30179
- "# HAR telemetry \u2192 Mission Control OTLP (disable: har telemetry off)",
30180
- "CLAUDE_CODE_ENABLE_TELEMETRY=1",
30181
- "OTEL_EXPORTER_OTLP_PROTOCOL=http/json",
30182
- `OTEL_EXPORTER_OTLP_ENDPOINT=${apiUrl}/api/otel`
30183
- );
30184
- if (signals.metrics) {
30185
- lines.push("OTEL_METRICS_EXPORTER=otlp");
30186
- }
30187
- if (signals.logs) {
30188
- lines.push("OTEL_LOGS_EXPORTER=otlp");
30189
- }
30190
- if (signals.prompts) {
30191
- lines.push(
30192
- "OTEL_LOG_USER_PROMPTS=1",
30193
- "OTEL_LOG_ASSISTANT_RESPONSES=1"
30194
- );
30195
- }
30196
- if (signals.traces) {
30197
- lines.push(
30198
- "OTEL_TRACES_EXPORTER=otlp",
30199
- "CLAUDE_CODE_ENHANCED_TELEMETRY_BETA=1"
30200
- );
30201
- }
30202
- }
30203
30540
  return lines.join("\n") + "\n";
30204
30541
  }
30205
30542
  var TELEMETRY_MARKER_START = "# HAR session attribution (generated)";
30206
30543
  var TELEMETRY_MARKER_END = "# end HAR telemetry";
30207
- function appendTelemetryEnvToFile(envFilePath, attrs, options) {
30208
- const block = buildTelemetryEnvBlock(attrs, options).trimEnd() + `
30544
+ function appendTelemetryEnvToFile(envFilePath, attrs) {
30545
+ const block = buildTelemetryEnvBlock(attrs).trimEnd() + `
30209
30546
  ${TELEMETRY_MARKER_END}
30210
30547
  `;
30211
30548
  let existing = "";
30212
- if (fs35.existsSync(envFilePath)) {
30213
- existing = fs35.readFileSync(envFilePath, "utf8");
30549
+ if (fs36.existsSync(envFilePath)) {
30550
+ existing = fs36.readFileSync(envFilePath, "utf8");
30214
30551
  const start = existing.indexOf(TELEMETRY_MARKER_START);
30215
30552
  if (start >= 0) {
30216
30553
  const end = existing.indexOf(TELEMETRY_MARKER_END, start);
@@ -30221,44 +30558,28 @@ ${TELEMETRY_MARKER_END}
30221
30558
  }
30222
30559
  }
30223
30560
  }
30224
- fs35.mkdirSync(path35.dirname(envFilePath), { recursive: true });
30561
+ fs36.mkdirSync(path36.dirname(envFilePath), { recursive: true });
30225
30562
  const combined = (existing.replace(/\n+$/, "\n") + block).replace(/^\n+/, "");
30226
- fs35.writeFileSync(envFilePath, combined.endsWith("\n") ? combined : combined + "\n");
30227
- }
30228
- function buildCodexOtelSnippet(attrs) {
30229
- const apiUrl = getControlApiUrl().replace(/\/$/, "");
30230
- const resource = buildOtelResourceAttributes(attrs);
30231
- return `# Merge into ~/.codex/config.toml (HAR does not overwrite your config)
30232
- # Generated for session ${attrs.sessionKey}
30233
-
30234
- [otel]
30235
- exporter = "otlp-http"
30236
- endpoint = "${apiUrl}/api/otel"
30237
- metrics_exporter = "otlp-http"
30238
-
30239
- # Optional: set resource attributes via environment when starting Codex:
30240
- # export OTEL_RESOURCE_ATTRIBUTES="${resource}"
30241
- # export HAR_SESSION_KEY="${attrs.sessionKey}"
30242
- `;
30563
+ fs36.writeFileSync(envFilePath, combined.endsWith("\n") ? combined : combined + "\n");
30243
30564
  }
30244
30565
 
30245
30566
  // src/core/usage-harvest/claude.ts
30246
30567
  function pathsMatch(candidate, targets) {
30247
- const norm = path36.resolve(candidate);
30568
+ const norm = path37.resolve(candidate);
30248
30569
  return targets.some((t2) => {
30249
- const target = path36.resolve(t2);
30250
- return norm === target || norm.startsWith(target + path36.sep) || target.startsWith(norm + path36.sep);
30570
+ const target = path37.resolve(t2);
30571
+ return norm === target || norm.startsWith(target + path37.sep) || target.startsWith(norm + path37.sep);
30251
30572
  });
30252
30573
  }
30253
30574
  function claudeProjectsRoot() {
30254
- return process.env.HAR_CLAUDE_PROJECTS_DIR ? path36.resolve(process.env.HAR_CLAUDE_PROJECTS_DIR) : path36.join(os10.homedir(), ".claude", "projects");
30575
+ return process.env.HAR_CLAUDE_PROJECTS_DIR ? path37.resolve(process.env.HAR_CLAUDE_PROJECTS_DIR) : path37.join(os10.homedir(), ".claude", "projects");
30255
30576
  }
30256
30577
  function encodeClaudeProjectDir(cwd) {
30257
30578
  return cwd.replace(/[^a-zA-Z0-9]/g, "-");
30258
30579
  }
30259
30580
  function readJsonlRecords(filePath) {
30260
- if (!fs36.existsSync(filePath)) return [];
30261
- const lines = fs36.readFileSync(filePath, "utf8").split(/\r?\n/);
30581
+ if (!fs37.existsSync(filePath)) return [];
30582
+ const lines = fs37.readFileSync(filePath, "utf8").split(/\r?\n/);
30262
30583
  const out = [];
30263
30584
  for (const line of lines) {
30264
30585
  const trimmed = line.trim();
@@ -30301,16 +30622,16 @@ function findMatchingClaudeTranscripts(slot) {
30301
30622
  const targets = [slot.workDir, slot.worktreePath].filter(Boolean);
30302
30623
  if (targets.length === 0) return [];
30303
30624
  const root = claudeProjectsRoot();
30304
- if (!fs36.existsSync(root)) return [];
30625
+ if (!fs37.existsSync(root)) return [];
30305
30626
  const matches = [];
30306
- for (const entry of fs36.readdirSync(root, { withFileTypes: true })) {
30627
+ for (const entry of fs37.readdirSync(root, { withFileTypes: true })) {
30307
30628
  if (!entry.isDirectory()) continue;
30308
- const projectDir = path36.join(root, entry.name);
30629
+ const projectDir = path37.join(root, entry.name);
30309
30630
  const encodedHit = targets.some((t2) => entry.name.includes(encodeClaudeProjectDir(t2).slice(0, 40)));
30310
- for (const file of fs36.readdirSync(projectDir)) {
30631
+ for (const file of fs37.readdirSync(projectDir)) {
30311
30632
  if (!file.endsWith(".jsonl")) continue;
30312
- const filePath = path36.join(projectDir, file);
30313
- const stat = fs36.statSync(filePath);
30633
+ const filePath = path37.join(projectDir, file);
30634
+ const stat = fs37.statSync(filePath);
30314
30635
  const records = readJsonlRecords(filePath);
30315
30636
  let cwdHit = encodedHit;
30316
30637
  for (const record2 of records) {
@@ -30421,30 +30742,30 @@ function harvestClaudeEvents(slot) {
30421
30742
  }
30422
30743
 
30423
30744
  // src/core/usage-harvest/codex.ts
30424
- var fs37 = __toESM(require("fs"));
30745
+ var fs38 = __toESM(require("fs"));
30425
30746
  var os11 = __toESM(require("os"));
30426
- var path37 = __toESM(require("path"));
30747
+ var path38 = __toESM(require("path"));
30427
30748
  function pathsMatch2(candidate, targets) {
30428
- const norm = path37.resolve(candidate);
30749
+ const norm = path38.resolve(candidate);
30429
30750
  return targets.some((t2) => {
30430
- const target = path37.resolve(t2);
30431
- return norm === target || norm.startsWith(target + path37.sep) || target.startsWith(norm + path37.sep);
30751
+ const target = path38.resolve(t2);
30752
+ return norm === target || norm.startsWith(target + path38.sep) || target.startsWith(norm + path38.sep);
30432
30753
  });
30433
30754
  }
30434
30755
  function codexSessionsRoot() {
30435
- return process.env.HAR_CODEX_SESSIONS_DIR ? path37.resolve(process.env.HAR_CODEX_SESSIONS_DIR) : path37.join(os11.homedir(), ".codex", "sessions");
30756
+ return process.env.HAR_CODEX_SESSIONS_DIR ? path38.resolve(process.env.HAR_CODEX_SESSIONS_DIR) : path38.join(os11.homedir(), ".codex", "sessions");
30436
30757
  }
30437
30758
  function walkJsonlFiles(dir, out = []) {
30438
- if (!fs37.existsSync(dir)) return out;
30439
- for (const entry of fs37.readdirSync(dir, { withFileTypes: true })) {
30440
- const full = path37.join(dir, entry.name);
30759
+ if (!fs38.existsSync(dir)) return out;
30760
+ for (const entry of fs38.readdirSync(dir, { withFileTypes: true })) {
30761
+ const full = path38.join(dir, entry.name);
30441
30762
  if (entry.isDirectory()) walkJsonlFiles(full, out);
30442
30763
  else if (entry.isFile() && entry.name.endsWith(".jsonl")) out.push(full);
30443
30764
  }
30444
30765
  return out;
30445
30766
  }
30446
30767
  function readJsonl(filePath) {
30447
- const lines = fs37.readFileSync(filePath, "utf8").split(/\r?\n/);
30768
+ const lines = fs38.readFileSync(filePath, "utf8").split(/\r?\n/);
30448
30769
  const out = [];
30449
30770
  for (const line of lines) {
30450
30771
  const trimmed = line.trim();
@@ -30508,7 +30829,7 @@ function harvestCodexUsage(slot) {
30508
30829
  const extracted = extractCodexTokens(records);
30509
30830
  if (!extracted.cwd || !pathsMatch2(extracted.cwd, targets)) continue;
30510
30831
  if (extracted.tokensInput + extracted.tokensOutput + extracted.tokensCacheRead === 0) continue;
30511
- const mtime = fs37.statSync(file).mtimeMs;
30832
+ const mtime = fs38.statSync(file).mtimeMs;
30512
30833
  if (mtime >= bestMtime) {
30513
30834
  bestMtime = mtime;
30514
30835
  best = extracted;
@@ -30578,7 +30899,7 @@ async function waitForControlApi(apiUrl = getControlApiUrl(), timeoutMs = 6e4, i
30578
30899
  const deadline = Date.now() + timeoutMs;
30579
30900
  while (Date.now() < deadline) {
30580
30901
  if (await isControlApiReachable(apiUrl)) return true;
30581
- await new Promise((resolve33) => setTimeout(resolve33, intervalMs));
30902
+ await new Promise((resolve35) => setTimeout(resolve35, intervalMs));
30582
30903
  }
30583
30904
  return false;
30584
30905
  }
@@ -30590,7 +30911,7 @@ async function syncAllKnownReposWithControl(options) {
30590
30911
  }
30591
30912
  const repoPaths = new Set(listRegisteredRepos());
30592
30913
  if (options?.cwd) {
30593
- const cwd = path38.resolve(options.cwd);
30914
+ const cwd = canonicalizeControlRepoPath(options.cwd);
30594
30915
  if (readManifest(cwd)) repoPaths.add(cwd);
30595
30916
  }
30596
30917
  try {
@@ -30598,7 +30919,7 @@ async function syncAllKnownReposWithControl(options) {
30598
30919
  if (listResponse.ok) {
30599
30920
  const repos = await listResponse.json();
30600
30921
  for (const repo of repos) {
30601
- const resolved = path38.resolve(repo.path);
30922
+ const resolved = canonicalizeControlRepoPath(repo.path);
30602
30923
  if (readManifest(resolved)) repoPaths.add(resolved);
30603
30924
  }
30604
30925
  }
@@ -30617,7 +30938,7 @@ async function syncAllKnownReposWithControl(options) {
30617
30938
  return { synced, failed };
30618
30939
  }
30619
30940
  async function registerRepoWithControl(options) {
30620
- const repoPath = path38.resolve(options.repoPath);
30941
+ const repoPath = canonicalizeControlRepoPath(options.repoPath);
30621
30942
  const apiUrl = options.apiUrl ?? getControlApiUrl();
30622
30943
  const manifest = readManifest(repoPath);
30623
30944
  const stagesRegistry = readStageRegistry(repoPath);
@@ -30630,7 +30951,7 @@ async function registerRepoWithControl(options) {
30630
30951
  return postJson(`${apiUrl}/api/repos`, body, options.dryRun);
30631
30952
  }
30632
30953
  async function syncRepoWithControl(options) {
30633
- const repoPath = path38.resolve(options.repoPath);
30954
+ const repoPath = canonicalizeControlRepoPath(options.repoPath);
30634
30955
  const apiUrl = options.apiUrl ?? getControlApiUrl();
30635
30956
  if (options.cloud) {
30636
30957
  const remote = createRemoteExecutor();
@@ -30658,7 +30979,7 @@ async function syncRepoWithControl(options) {
30658
30979
  const listResponse = await fetch(`${apiUrl}/api/repos`);
30659
30980
  if (!listResponse.ok) throw new Error("Failed to list repos from Control API");
30660
30981
  const repos = await listResponse.json();
30661
- const existing = repos.find((r2) => path38.resolve(r2.path) === repoPath);
30982
+ const existing = repos.find((r2) => path39.resolve(r2.path) === repoPath);
30662
30983
  if (!existing) throw new Error(`Repo not registered: ${repoPath}`);
30663
30984
  await syncRepoRunsAndSlots(apiUrl, existing.id, repoPath, options.dryRun);
30664
30985
  return;
@@ -30749,7 +31070,12 @@ async function syncRepoWithControlAsync(repoPath) {
30749
31070
  }
30750
31071
  return;
30751
31072
  }
30752
- await withTimeout(syncRepoWithControl({ repoPath, apiUrl }), SYNC_TIMEOUT_MS, "control sync");
31073
+ const canonical = canonicalizeControlRepoPath(repoPath);
31074
+ await withTimeout(
31075
+ syncRepoWithControl({ repoPath: canonical, apiUrl }),
31076
+ SYNC_TIMEOUT_MS,
31077
+ "control sync"
31078
+ );
30753
31079
  } catch (err) {
30754
31080
  if (verbose) {
30755
31081
  const message = err instanceof Error ? err.message : String(err);
@@ -30775,9 +31101,12 @@ function checkLaunchGuard2(repoPath, agentId, options = {}) {
30775
31101
  return { allowed: true, slot: occupied.slot, readiness };
30776
31102
  }
30777
31103
 
31104
+ // src/core/telemetry-ensure.ts
31105
+ init_control_config();
31106
+
30778
31107
  // src/core/control-lifecycle.ts
30779
31108
  var import_child_process8 = require("child_process");
30780
- var path39 = __toESM(require("path"));
31109
+ var path40 = __toESM(require("path"));
30781
31110
 
30782
31111
  // src/core/control-image.ts
30783
31112
  var DEFAULT_CONTROL_IMAGE = "theosfactory/har-control";
@@ -30795,15 +31124,16 @@ function shouldBuildControlLocally() {
30795
31124
  }
30796
31125
 
30797
31126
  // src/core/control-lifecycle.ts
31127
+ init_control_config();
30798
31128
  var CONTROL_CONTAINER_NAME = "har-control";
30799
31129
  var CONTROL_DATA_VOLUME = "har_control_data";
30800
31130
  var CONTROL_CONTAINER_PORT = 3847;
30801
31131
  var CONTROL_DATA_DB_URL = "file:/data/har_control.db";
30802
31132
  function resolveControlDir() {
30803
- return path39.resolve(__dirname, "..", "control");
31133
+ return path40.resolve(__dirname, "..", "control");
30804
31134
  }
30805
31135
  function resolveControlBuildContext() {
30806
- return path39.resolve(resolveControlDir(), "..");
31136
+ return path40.resolve(resolveControlDir(), "..");
30807
31137
  }
30808
31138
  function buildDockerRunArgs(options) {
30809
31139
  const name = options.containerName ?? CONTROL_CONTAINER_NAME;
@@ -30859,7 +31189,7 @@ async function startMissionControl(options) {
30859
31189
  "-t",
30860
31190
  imageRef,
30861
31191
  "-f",
30862
- path39.join(resolveControlDir(), "Dockerfile"),
31192
+ path40.join(resolveControlDir(), "Dockerfile"),
30863
31193
  resolveControlBuildContext()
30864
31194
  ]);
30865
31195
  if (buildCode !== 0) {
@@ -30903,6 +31233,7 @@ async function startControlAndSync(options) {
30903
31233
  }
30904
31234
 
30905
31235
  // src/core/telemetry-ensure.ts
31236
+ init_telemetry_config();
30906
31237
  async function ensureTelemetryInfrastructure(options) {
30907
31238
  const apiUrl = getControlApiUrl();
30908
31239
  const telemetryEnabled = isTelemetryEnabled();
@@ -31001,7 +31332,8 @@ async function ensureTelemetryInfrastructure(options) {
31001
31332
  }
31002
31333
 
31003
31334
  // src/core/run-service.ts
31004
- var path40 = __toESM(require("path"));
31335
+ init_telemetry_config();
31336
+ var path42 = __toESM(require("path"));
31005
31337
  function extractShellOutput(result) {
31006
31338
  const stdout = result.logs?.find((log) => log.stream === "stdout")?.content ?? result.logs?.find((log) => !log.stream)?.content ?? "";
31007
31339
  const stderr = result.logs?.find((log) => log.stream === "stderr")?.content ?? "";
@@ -31117,23 +31449,31 @@ var RunService = class {
31117
31449
  worktreePath: slot.worktreePath,
31118
31450
  branch: slot.branch,
31119
31451
  dirty: slot.dirty,
31120
- sessionCreatedAt: slot.sessionCreatedAt,
31121
- purpose: slot.purpose
31452
+ sessionCreatedAt: slot.sessionCreatedAt
31122
31453
  } : void 0
31123
31454
  };
31124
31455
  }
31125
31456
  let telemetryBanner = "";
31126
- let otelReady = false;
31127
31457
  if (isTelemetryEnabled() && process.env.NODE_ENV !== "test") {
31128
31458
  const ensured = await ensureTelemetryInfrastructure({ startIfNeeded: true });
31129
- otelReady = ensured.otelReady;
31130
31459
  if (ensured.message) {
31131
31460
  telemetryBanner += `${ensured.message}
31132
- Usage from Claude Code / Codex will appear under Worktrees. Disable: har telemetry off
31461
+ Usage from Cursor / Claude / Codex (opentelemetry-hooks) will appear under Worktrees. Disable: har telemetry off
31133
31462
  `;
31134
31463
  }
31135
31464
  if (ensured.warning) {
31136
31465
  telemetryBanner += `${ensured.warning}
31466
+ `;
31467
+ }
31468
+ try {
31469
+ const { ensureOtelHooks: ensureOtelHooks2 } = await Promise.resolve().then(() => (init_otel_hooks(), otel_hooks_exports));
31470
+ const hooks = ensureOtelHooks2({ setupAgents: true });
31471
+ if (hooks.message) telemetryBanner += `${hooks.message}
31472
+ `;
31473
+ if (hooks.warning) telemetryBanner += `${hooks.warning}
31474
+ `;
31475
+ } catch (err) {
31476
+ telemetryBanner += `opentelemetry-hooks setup skipped: ${err instanceof Error ? err.message : String(err)}
31137
31477
  `;
31138
31478
  }
31139
31479
  }
@@ -31147,8 +31487,7 @@ Usage from Claude Code / Codex will appear under Worktrees. Disable: har telemet
31147
31487
  claude: options.claude,
31148
31488
  confirmReplace: options.confirmReplace,
31149
31489
  force: options.force,
31150
- resume: options.resume,
31151
- purpose: options.purpose
31490
+ resume: options.resume
31152
31491
  },
31153
31492
  trigger: "cli"
31154
31493
  });
@@ -31167,21 +31506,16 @@ Usage from Claude Code / Codex will appear under Worktrees. Disable: har telemet
31167
31506
  suffix: session.suffix,
31168
31507
  createdAt: session.createdAt
31169
31508
  });
31170
- const envFile = path40.join(session.workDir, `.env.agent.${options.agentId}`);
31509
+ const envFile = path42.join(session.workDir, `.env.agent.${options.agentId}`);
31171
31510
  try {
31172
- appendTelemetryEnvToFile(
31173
- envFile,
31174
- {
31175
- sessionKey,
31176
- agentId: options.agentId,
31177
- repoPath: path40.resolve(options.repoPath),
31178
- workDir: session.workDir,
31179
- branch: session.branch,
31180
- suffix: session.suffix,
31181
- purpose: session.purpose
31182
- },
31183
- { otelReady: otelReady && isTelemetryEnabled() }
31184
- );
31511
+ appendTelemetryEnvToFile(envFile, {
31512
+ sessionKey,
31513
+ agentId: options.agentId,
31514
+ repoPath: path42.resolve(options.repoPath),
31515
+ workDir: session.workDir,
31516
+ branch: session.branch,
31517
+ suffix: session.suffix
31518
+ });
31185
31519
  } catch {
31186
31520
  }
31187
31521
  }
@@ -31467,9 +31801,6 @@ var envCommand = {
31467
31801
  type: "boolean",
31468
31802
  default: false,
31469
31803
  describe: "Resume a failed or partial launch without creating a new worktree"
31470
- }).option("purpose", {
31471
- type: "string",
31472
- describe: "Optional session label stored in the slot registry (shown in status and occupied-slot warnings)"
31473
31804
  }),
31474
31805
  handleLaunch
31475
31806
  ).command(
@@ -31543,7 +31874,7 @@ var envCommand = {
31543
31874
  }
31544
31875
  };
31545
31876
  async function handleInit(argv) {
31546
- const repoPath = path41.resolve(argv.repo);
31877
+ const repoPath = path43.resolve(argv.repo);
31547
31878
  header("har env init");
31548
31879
  info(`Repository: ${repoPath}`);
31549
31880
  try {
@@ -31601,7 +31932,7 @@ async function handleInit(argv) {
31601
31932
  }
31602
31933
  }
31603
31934
  async function handleMaintain(argv) {
31604
- const repoPath = path41.resolve(argv.repo);
31935
+ const repoPath = path43.resolve(argv.repo);
31605
31936
  header("har env maintain");
31606
31937
  info(`Repository: ${repoPath}`);
31607
31938
  try {
@@ -31699,7 +32030,7 @@ async function handleAgentMdProposal(repoPath, autoYes) {
31699
32030
  if (autoYes) {
31700
32031
  const proposal = readAgentMdProposal(repoPath);
31701
32032
  if (proposal) {
31702
- writeFileSafe(path41.join(repoPath, "AGENT.md"), proposal.content);
32033
+ writeFileSafe(path43.join(repoPath, "AGENT.md"), proposal.content);
31703
32034
  clearAgentMdProposal(repoPath);
31704
32035
  info("Applied AGENT.md proposal (--yes)");
31705
32036
  }
@@ -31708,7 +32039,7 @@ async function handleAgentMdProposal(repoPath, autoYes) {
31708
32039
  await promptApplyAgentMdProposal(repoPath);
31709
32040
  }
31710
32041
  async function handleAddStage(argv) {
31711
- const repoPath = path41.resolve(argv.repo);
32042
+ const repoPath = path43.resolve(argv.repo);
31712
32043
  const available = listStageTemplateIds();
31713
32044
  if (argv.list) {
31714
32045
  for (const id of available) {
@@ -31785,7 +32116,7 @@ async function handleAddStage(argv) {
31785
32116
  }
31786
32117
  }
31787
32118
  async function handlePreflight(argv) {
31788
- const repo = path41.resolve(argv.repo);
32119
+ const repo = path43.resolve(argv.repo);
31789
32120
  const agentId = validateAgentId(argv.id, repo);
31790
32121
  const result = await preflightEnvironment({
31791
32122
  repoPath: repo,
@@ -31803,7 +32134,7 @@ async function handlePreflight(argv) {
31803
32134
  process.exit(result.code);
31804
32135
  }
31805
32136
  async function handleLaunch(argv) {
31806
- const repo = path41.resolve(argv.repo);
32137
+ const repo = path43.resolve(argv.repo);
31807
32138
  const agentId = validateAgentId(argv.id, repo);
31808
32139
  let confirmReplace = argv.replace;
31809
32140
  let force = argv.force;
@@ -31815,8 +32146,8 @@ async function handleLaunch(argv) {
31815
32146
  console.error(guard.reason ?? "");
31816
32147
  const readline5 = await import("readline");
31817
32148
  const rl = readline5.createInterface({ input: process.stdin, output: process.stderr });
31818
- const answer = await new Promise((resolve33) => {
31819
- rl.question("Replace this slot? [y/N] ", resolve33);
32149
+ const answer = await new Promise((resolve35) => {
32150
+ rl.question("Replace this slot? [y/N] ", resolve35);
31820
32151
  });
31821
32152
  rl.close();
31822
32153
  if (!/^[Yy]$/.test(answer.trim())) {
@@ -31832,8 +32163,8 @@ async function handleLaunch(argv) {
31832
32163
  warn("Worktree has uncommitted changes.");
31833
32164
  const readline5 = await import("readline");
31834
32165
  const rl = readline5.createInterface({ input: process.stdin, output: process.stderr });
31835
- const answer = await new Promise((resolve33) => {
31836
- rl.question("Discard uncommitted changes? [y/N] ", resolve33);
32166
+ const answer = await new Promise((resolve35) => {
32167
+ rl.question("Discard uncommitted changes? [y/N] ", resolve35);
31837
32168
  });
31838
32169
  rl.close();
31839
32170
  if (!/^[Yy]$/.test(answer.trim())) {
@@ -31855,7 +32186,6 @@ async function handleLaunch(argv) {
31855
32186
  confirmReplace,
31856
32187
  force,
31857
32188
  resume: argv.resume,
31858
- purpose: argv.purpose,
31859
32189
  capture: false
31860
32190
  });
31861
32191
  if (result.blocked) {
@@ -31888,7 +32218,7 @@ async function handleRecover(argv) {
31888
32218
  });
31889
32219
  }
31890
32220
  async function handleVerify(argv) {
31891
- const repo = path41.resolve(argv.repo);
32221
+ const repo = path43.resolve(argv.repo);
31892
32222
  const agentId = validateAgentId(argv.id, repo);
31893
32223
  const result = await runVerification({
31894
32224
  repoPath: repo,
@@ -31900,7 +32230,7 @@ async function handleVerify(argv) {
31900
32230
  process.exit(result.code);
31901
32231
  }
31902
32232
  async function handleTeardown(argv) {
31903
- const repo = path41.resolve(argv.repo);
32233
+ const repo = path43.resolve(argv.repo);
31904
32234
  const agentId = validateAgentId(argv.id, repo);
31905
32235
  const result = await teardownEnvironment({
31906
32236
  repoPath: repo,
@@ -31911,7 +32241,7 @@ async function handleTeardown(argv) {
31911
32241
  process.exit(result.code);
31912
32242
  }
31913
32243
  async function handleComplete(argv) {
31914
- const repo = path41.resolve(argv.repo);
32244
+ const repo = path43.resolve(argv.repo);
31915
32245
  const agentId = validateAgentId(argv.id, repo);
31916
32246
  const result = await completeEnvironment({
31917
32247
  repoPath: repo,
@@ -31932,7 +32262,7 @@ async function handleComplete(argv) {
31932
32262
  process.exit(result.code);
31933
32263
  }
31934
32264
  async function handleStatus(argv) {
31935
- const repoPath = path41.resolve(argv.repo);
32265
+ const repoPath = path43.resolve(argv.repo);
31936
32266
  if (argv.json) {
31937
32267
  const status = collectEnvironmentStatus(repoPath);
31938
32268
  const output = EnvironmentStatusSchema.parse(status);
@@ -31945,7 +32275,7 @@ async function handleStatus(argv) {
31945
32275
  });
31946
32276
  }
31947
32277
  async function handleRunsList(argv) {
31948
- const repoPath = path41.resolve(argv.repo);
32278
+ const repoPath = path43.resolve(argv.repo);
31949
32279
  const runs = listRuns(repoPath, { stageId: argv.stage, limit: argv.limit });
31950
32280
  if (argv.json) {
31951
32281
  process.stdout.write(JSON.stringify({ runs }, null, 2) + "\n");
@@ -31962,7 +32292,7 @@ async function handleRunsList(argv) {
31962
32292
  }
31963
32293
  }
31964
32294
  async function handleRunsGet(argv) {
31965
- const run2 = getRun(path41.resolve(argv.repo), argv.runId);
32295
+ const run2 = getRun(path43.resolve(argv.repo), argv.runId);
31966
32296
  if (!run2) {
31967
32297
  error(`Run not found: ${argv.runId}`);
31968
32298
  process.exit(1);
@@ -32041,7 +32371,8 @@ function printNextSteps(auto2) {
32041
32371
  }
32042
32372
 
32043
32373
  // src/cli/commands/control.ts
32044
- var path42 = __toESM(require("path"));
32374
+ var path44 = __toESM(require("path"));
32375
+ init_control_config();
32045
32376
  var controlCommand = {
32046
32377
  command: "control <subcommand>",
32047
32378
  describe: "Mission Control dashboard (local)",
@@ -32144,7 +32475,7 @@ async function handleDown() {
32144
32475
  process.exit(code);
32145
32476
  }
32146
32477
  async function handleRegister(argv) {
32147
- const repoPath = path42.resolve(argv.repo);
32478
+ const repoPath = path44.resolve(argv.repo);
32148
32479
  header("har control register");
32149
32480
  info(`Repository: ${repoPath}`);
32150
32481
  try {
@@ -32164,7 +32495,7 @@ async function handleRegister(argv) {
32164
32495
  }
32165
32496
  }
32166
32497
  async function handleSync(argv) {
32167
- const repoPath = path42.resolve(argv.repo);
32498
+ const repoPath = path44.resolve(argv.repo);
32168
32499
  try {
32169
32500
  await syncRepoWithControl({
32170
32501
  repoPath,
@@ -32203,7 +32534,7 @@ async function handleWatch(argv) {
32203
32534
  };
32204
32535
  const tick = async () => {
32205
32536
  if (argv.repo) {
32206
- await syncOne(path42.resolve(argv.repo));
32537
+ await syncOne(path44.resolve(argv.repo));
32207
32538
  return;
32208
32539
  }
32209
32540
  try {
@@ -32234,12 +32565,12 @@ async function handleLogin(argv) {
32234
32565
  }
32235
32566
 
32236
32567
  // src/cli/commands/hooks.ts
32237
- var path45 = __toESM(require("path"));
32568
+ var path47 = __toESM(require("path"));
32238
32569
 
32239
32570
  // src/core/hooks.ts
32240
- var fs38 = __toESM(require("fs"));
32241
- var os12 = __toESM(require("os"));
32242
- var path43 = __toESM(require("path"));
32571
+ var fs40 = __toESM(require("fs"));
32572
+ var os13 = __toESM(require("os"));
32573
+ var path45 = __toESM(require("path"));
32243
32574
  var MARKER_START = "# >>> har commit gate (managed by `har hooks`) >>>";
32244
32575
  var MARKER_END = "# <<< har commit gate <<<";
32245
32576
  var PRE_COMMIT_BLOCK = [
@@ -32254,27 +32585,27 @@ var POST_COMMIT_BLOCK = [
32254
32585
  '"$(git rev-parse --git-path hooks)/har-post-commit" || true',
32255
32586
  MARKER_END
32256
32587
  ].join("\n");
32257
- function tryGit2(cwd, args) {
32588
+ function tryGit3(cwd, args) {
32258
32589
  const result = run(`git ${args}`, { cwd });
32259
32590
  return result.code === 0 ? result.stdout.trim() : void 0;
32260
32591
  }
32261
32592
  function resolveCheckoutRoot(cwd) {
32262
- return tryGit2(cwd, "rev-parse --show-toplevel");
32593
+ return tryGit3(cwd, "rev-parse --show-toplevel");
32263
32594
  }
32264
32595
  function resolveHooksDir(checkoutDir) {
32265
- const hooksPath = tryGit2(checkoutDir, "rev-parse --git-path hooks");
32596
+ const hooksPath = tryGit3(checkoutDir, "rev-parse --git-path hooks");
32266
32597
  if (!hooksPath) throw new Error(`Not a git checkout: ${checkoutDir}`);
32267
- return path43.resolve(checkoutDir, hooksPath);
32598
+ return path45.resolve(checkoutDir, hooksPath);
32268
32599
  }
32269
32600
  function getConfiguredHooksPath(checkoutDir) {
32270
- return tryGit2(checkoutDir, "config core.hooksPath") || void 0;
32601
+ return tryGit3(checkoutDir, "config core.hooksPath") || void 0;
32271
32602
  }
32272
32603
  function defaultHarInvocation() {
32273
32604
  const entry = process.argv[1];
32274
32605
  if (entry && entry.endsWith(".js")) {
32275
- return `"${process.execPath}" "${path43.resolve(entry)}"`;
32606
+ return `"${process.execPath}" "${path45.resolve(entry)}"`;
32276
32607
  }
32277
- if (entry) return `"${path43.resolve(entry)}"`;
32608
+ if (entry) return `"${path45.resolve(entry)}"`;
32278
32609
  return "har";
32279
32610
  }
32280
32611
  function buildHookScript(harInvocation, subcommand, failOpenNotice) {
@@ -32295,40 +32626,40 @@ exit 0
32295
32626
  `;
32296
32627
  }
32297
32628
  function upsertMarkedBlock(filePath, block) {
32298
- if (!fs38.existsSync(filePath)) {
32299
- fs38.writeFileSync(filePath, `#!/bin/sh
32629
+ if (!fs40.existsSync(filePath)) {
32630
+ fs40.writeFileSync(filePath, `#!/bin/sh
32300
32631
  ${block}
32301
32632
  `, { mode: 493 });
32302
32633
  return "created";
32303
32634
  }
32304
- const content = fs38.readFileSync(filePath, "utf8");
32635
+ const content = fs40.readFileSync(filePath, "utf8");
32305
32636
  if (content.includes(MARKER_START)) {
32306
32637
  const pattern = new RegExp(
32307
32638
  `${escapeRegExp(MARKER_START)}[\\s\\S]*?${escapeRegExp(MARKER_END)}`
32308
32639
  );
32309
- fs38.writeFileSync(filePath, content.replace(pattern, block));
32310
- fs38.chmodSync(filePath, 493);
32640
+ fs40.writeFileSync(filePath, content.replace(pattern, block));
32641
+ fs40.chmodSync(filePath, 493);
32311
32642
  return "updated";
32312
32643
  }
32313
32644
  const suffix = content.endsWith("\n") ? "" : "\n";
32314
- fs38.writeFileSync(filePath, `${content}${suffix}
32645
+ fs40.writeFileSync(filePath, `${content}${suffix}
32315
32646
  ${block}
32316
32647
  `);
32317
- fs38.chmodSync(filePath, 493);
32648
+ fs40.chmodSync(filePath, 493);
32318
32649
  return "appended";
32319
32650
  }
32320
32651
  function removeMarkedBlock(filePath) {
32321
- if (!fs38.existsSync(filePath)) return false;
32322
- const content = fs38.readFileSync(filePath, "utf8");
32652
+ if (!fs40.existsSync(filePath)) return false;
32653
+ const content = fs40.readFileSync(filePath, "utf8");
32323
32654
  if (!content.includes(MARKER_START)) return false;
32324
32655
  const pattern = new RegExp(
32325
32656
  `\\n?${escapeRegExp(MARKER_START)}[\\s\\S]*?${escapeRegExp(MARKER_END)}\\n?`
32326
32657
  );
32327
32658
  const stripped = content.replace(pattern, "\n");
32328
32659
  if (stripped.replace(/^#!\/bin\/sh\n?/, "").trim() === "") {
32329
- fs38.rmSync(filePath);
32660
+ fs40.rmSync(filePath);
32330
32661
  } else {
32331
- fs38.writeFileSync(filePath, stripped);
32662
+ fs40.writeFileSync(filePath, stripped);
32332
32663
  }
32333
32664
  return true;
32334
32665
  }
@@ -32350,20 +32681,20 @@ Or re-run with --force to write into that directory anyway.`
32350
32681
  );
32351
32682
  }
32352
32683
  const hooksDir = resolveHooksDir(checkout);
32353
- fs38.mkdirSync(hooksDir, { recursive: true });
32684
+ fs40.mkdirSync(hooksDir, { recursive: true });
32354
32685
  const invocation = options.harInvocation ?? defaultHarInvocation();
32355
- fs38.writeFileSync(
32356
- path43.join(hooksDir, "har-pre-commit"),
32686
+ fs40.writeFileSync(
32687
+ path45.join(hooksDir, "har-pre-commit"),
32357
32688
  buildHookScript(invocation, "check", "har: binary not found; skipping commit gate (reinstall with har hooks install)"),
32358
32689
  { mode: 493 }
32359
32690
  );
32360
- fs38.writeFileSync(
32361
- path43.join(hooksDir, "har-post-commit"),
32691
+ fs40.writeFileSync(
32692
+ path45.join(hooksDir, "har-post-commit"),
32362
32693
  buildHookScript(invocation, "record-commit", "har: binary not found; skipping commit association"),
32363
32694
  { mode: 493 }
32364
32695
  );
32365
- const preCommit = upsertMarkedBlock(path43.join(hooksDir, "pre-commit"), PRE_COMMIT_BLOCK);
32366
- const postCommit = upsertMarkedBlock(path43.join(hooksDir, "post-commit"), POST_COMMIT_BLOCK);
32696
+ const preCommit = upsertMarkedBlock(path45.join(hooksDir, "pre-commit"), PRE_COMMIT_BLOCK);
32697
+ const postCommit = upsertMarkedBlock(path45.join(hooksDir, "post-commit"), POST_COMMIT_BLOCK);
32367
32698
  ensureValidationsIgnored(checkout);
32368
32699
  return { hooksDir, preCommit, postCommit };
32369
32700
  }
@@ -32372,12 +32703,12 @@ function uninstallHooks(repoPath) {
32372
32703
  if (!checkout) throw new Error(`Not a git repository: ${repoPath}`);
32373
32704
  const hooksDir = resolveHooksDir(checkout);
32374
32705
  let removed = false;
32375
- removed = removeMarkedBlock(path43.join(hooksDir, "pre-commit")) || removed;
32376
- removed = removeMarkedBlock(path43.join(hooksDir, "post-commit")) || removed;
32706
+ removed = removeMarkedBlock(path45.join(hooksDir, "pre-commit")) || removed;
32707
+ removed = removeMarkedBlock(path45.join(hooksDir, "post-commit")) || removed;
32377
32708
  for (const file of ["har-pre-commit", "har-post-commit"]) {
32378
- const full = path43.join(hooksDir, file);
32379
- if (fs38.existsSync(full)) {
32380
- fs38.rmSync(full);
32709
+ const full = path45.join(hooksDir, file);
32710
+ if (fs40.existsSync(full)) {
32711
+ fs40.rmSync(full);
32381
32712
  removed = true;
32382
32713
  }
32383
32714
  }
@@ -32394,9 +32725,9 @@ function getCommitGateConfig(checkoutDir) {
32394
32725
  function isAgentWorktree(checkoutDir) {
32395
32726
  const branch = getCurrentBranch(checkoutDir);
32396
32727
  if (branch && /^har-agent-\d+$/.test(branch)) return true;
32397
- const worktreesRoot = path43.join(os12.homedir(), "worktrees");
32398
- const resolved = path43.resolve(checkoutDir);
32399
- return resolved.startsWith(`${worktreesRoot}${path43.sep}`) && /-agent-\d+$/.test(path43.basename(resolved));
32728
+ const worktreesRoot = path45.join(os13.homedir(), "worktrees");
32729
+ const resolved = path45.resolve(checkoutDir);
32730
+ return resolved.startsWith(`${worktreesRoot}${path45.sep}`) && /-agent-\d+$/.test(path45.basename(resolved));
32400
32731
  }
32401
32732
  function resolveEffectiveMode(checkoutDir, gate) {
32402
32733
  if (!gate.enabled) return "off";
@@ -32409,15 +32740,15 @@ function getHooksStatus(repoPath) {
32409
32740
  const hooksDir = resolveHooksDir(checkout);
32410
32741
  const gate = getCommitGateConfig(checkout);
32411
32742
  const hasBlock = (name) => {
32412
- const file = path43.join(hooksDir, name);
32413
- return fs38.existsSync(file) && fs38.readFileSync(file, "utf8").includes(MARKER_START);
32743
+ const file = path45.join(hooksDir, name);
32744
+ return fs40.existsSync(file) && fs40.readFileSync(file, "utf8").includes(MARKER_START);
32414
32745
  };
32415
32746
  return {
32416
32747
  checkout,
32417
32748
  hooksDir,
32418
32749
  configuredHooksPath: getConfiguredHooksPath(checkout),
32419
- preCommitInstalled: hasBlock("pre-commit") && fs38.existsSync(path43.join(hooksDir, "har-pre-commit")),
32420
- postCommitInstalled: hasBlock("post-commit") && fs38.existsSync(path43.join(hooksDir, "har-post-commit")),
32750
+ preCommitInstalled: hasBlock("pre-commit") && fs40.existsSync(path45.join(hooksDir, "har-pre-commit")),
32751
+ postCommitInstalled: hasBlock("post-commit") && fs40.existsSync(path45.join(hooksDir, "har-post-commit")),
32421
32752
  gate,
32422
32753
  effectiveMode: resolveEffectiveMode(checkout, gate)
32423
32754
  };
@@ -32428,7 +32759,7 @@ function checkCommitGate(cwd) {
32428
32759
  }
32429
32760
  const checkout = resolveCheckoutRoot(cwd);
32430
32761
  if (!checkout) return { exitCode: 0, messages: [] };
32431
- if (!fs38.existsSync(path43.join(checkout, ".har", "stages.json"))) {
32762
+ if (!fs40.existsSync(path45.join(checkout, ".har", "stages.json"))) {
32432
32763
  return { exitCode: 0, messages: [] };
32433
32764
  }
32434
32765
  const gate = getCommitGateConfig(checkout);
@@ -32485,7 +32816,7 @@ async function recordCommitAssociation(cwd) {
32485
32816
  const checkout = resolveCheckoutRoot(cwd);
32486
32817
  if (!checkout) return { attached: false };
32487
32818
  const commitSha = getHeadSha(checkout);
32488
- const tree = tryGit2(checkout, "rev-parse HEAD^{tree}");
32819
+ const tree = tryGit3(checkout, "rev-parse HEAD^{tree}");
32489
32820
  if (!commitSha || !tree) return { attached: false };
32490
32821
  const updated = attachCommit(checkout, tree, commitSha);
32491
32822
  if (updated) {
@@ -32499,8 +32830,8 @@ async function recordCommitAssociation(cwd) {
32499
32830
  }
32500
32831
 
32501
32832
  // src/core/claude-hooks.ts
32502
- var fs39 = __toESM(require("fs"));
32503
- var path44 = __toESM(require("path"));
32833
+ var fs41 = __toESM(require("fs"));
32834
+ var path46 = __toESM(require("path"));
32504
32835
  var CLAUDE_GUARD_RELATIVE_PATH = ".har/hooks/claude-worktree-guard.sh";
32505
32836
  var CLAUDE_SETTINGS_RELATIVE_PATH = ".claude/settings.json";
32506
32837
  var GUARD_MATCHER = "Edit|Write|MultiEdit|NotebookEdit";
@@ -32511,8 +32842,8 @@ function isHarGuardEntry(entry) {
32511
32842
  );
32512
32843
  }
32513
32844
  function readSettings(settingsPath) {
32514
- if (!fs39.existsSync(settingsPath)) return {};
32515
- const raw = fs39.readFileSync(settingsPath, "utf8").trim();
32845
+ if (!fs41.existsSync(settingsPath)) return {};
32846
+ const raw = fs41.readFileSync(settingsPath, "utf8").trim();
32516
32847
  if (raw === "") return {};
32517
32848
  const parsed = JSON.parse(raw);
32518
32849
  if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
@@ -32521,19 +32852,19 @@ function readSettings(settingsPath) {
32521
32852
  return parsed;
32522
32853
  }
32523
32854
  function writeSettings(settingsPath, settings) {
32524
- fs39.mkdirSync(path44.dirname(settingsPath), { recursive: true });
32525
- fs39.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n");
32855
+ fs41.mkdirSync(path46.dirname(settingsPath), { recursive: true });
32856
+ fs41.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n");
32526
32857
  }
32527
32858
  function installClaudeGuard(repoPath) {
32528
32859
  const templatePath = resolveTemplateFile("claude-worktree-guard.sh.template");
32529
32860
  if (!templatePath) {
32530
32861
  throw new Error("Guard template not found (claude-worktree-guard.sh.template). Run npm run build.");
32531
32862
  }
32532
- const guardScript = path44.join(repoPath, CLAUDE_GUARD_RELATIVE_PATH);
32533
- fs39.mkdirSync(path44.dirname(guardScript), { recursive: true });
32534
- fs39.copyFileSync(templatePath, guardScript);
32535
- fs39.chmodSync(guardScript, 493);
32536
- const settingsPath = path44.join(repoPath, CLAUDE_SETTINGS_RELATIVE_PATH);
32863
+ const guardScript = path46.join(repoPath, CLAUDE_GUARD_RELATIVE_PATH);
32864
+ fs41.mkdirSync(path46.dirname(guardScript), { recursive: true });
32865
+ fs41.copyFileSync(templatePath, guardScript);
32866
+ fs41.chmodSync(guardScript, 493);
32867
+ const settingsPath = path46.join(repoPath, CLAUDE_SETTINGS_RELATIVE_PATH);
32537
32868
  const settings = readSettings(settingsPath);
32538
32869
  const hooks = settings.hooks ?? {};
32539
32870
  const preToolUse = (hooks.PreToolUse ?? []).filter((entry) => !isHarGuardEntry(entry));
@@ -32547,15 +32878,15 @@ function installClaudeGuard(repoPath) {
32547
32878
  }
32548
32879
  function uninstallClaudeGuard(repoPath) {
32549
32880
  let removed = false;
32550
- const guardScript = path44.join(repoPath, CLAUDE_GUARD_RELATIVE_PATH);
32551
- if (fs39.existsSync(guardScript)) {
32552
- fs39.rmSync(guardScript);
32881
+ const guardScript = path46.join(repoPath, CLAUDE_GUARD_RELATIVE_PATH);
32882
+ if (fs41.existsSync(guardScript)) {
32883
+ fs41.rmSync(guardScript);
32553
32884
  removed = true;
32554
- const parent = path44.dirname(guardScript);
32555
- if (fs39.existsSync(parent) && fs39.readdirSync(parent).length === 0) fs39.rmdirSync(parent);
32885
+ const parent = path46.dirname(guardScript);
32886
+ if (fs41.existsSync(parent) && fs41.readdirSync(parent).length === 0) fs41.rmdirSync(parent);
32556
32887
  }
32557
- const settingsPath = path44.join(repoPath, CLAUDE_SETTINGS_RELATIVE_PATH);
32558
- if (fs39.existsSync(settingsPath)) {
32888
+ const settingsPath = path46.join(repoPath, CLAUDE_SETTINGS_RELATIVE_PATH);
32889
+ if (fs41.existsSync(settingsPath)) {
32559
32890
  const settings = readSettings(settingsPath);
32560
32891
  const preToolUse = settings.hooks?.PreToolUse;
32561
32892
  if (preToolUse) {
@@ -32580,8 +32911,8 @@ function uninstallClaudeGuard(repoPath) {
32580
32911
  return { removed };
32581
32912
  }
32582
32913
  function claudeGuardInstalled(repoPath) {
32583
- const settingsPath = path44.join(repoPath, CLAUDE_SETTINGS_RELATIVE_PATH);
32584
- if (!fs39.existsSync(settingsPath)) return false;
32914
+ const settingsPath = path46.join(repoPath, CLAUDE_SETTINGS_RELATIVE_PATH);
32915
+ if (!fs41.existsSync(settingsPath)) return false;
32585
32916
  try {
32586
32917
  const settings = readSettings(settingsPath);
32587
32918
  return (settings.hooks?.PreToolUse ?? []).some(isHarGuardEntry);
@@ -32597,7 +32928,7 @@ function repoOption(y2) {
32597
32928
  function handleInstall2(argv) {
32598
32929
  try {
32599
32930
  if (argv.claude) {
32600
- const result2 = installClaudeGuard(path45.resolve(argv.repo));
32931
+ const result2 = installClaudeGuard(path47.resolve(argv.repo));
32601
32932
  success("Claude Code worktree guard installed.");
32602
32933
  info(`Guard script: ${result2.guardScript}`);
32603
32934
  info(`Settings: ${result2.settingsPath} (PreToolUse: Edit|Write|MultiEdit|NotebookEdit)`);
@@ -32605,7 +32936,7 @@ function handleInstall2(argv) {
32605
32936
  info("Commit both files so the guard travels with the repo.");
32606
32937
  return;
32607
32938
  }
32608
- const result = installHooks({ repoPath: path45.resolve(argv.repo), force: argv.force });
32939
+ const result = installHooks({ repoPath: path47.resolve(argv.repo), force: argv.force });
32609
32940
  success(`Commit gate installed in ${result.hooksDir}`);
32610
32941
  info(`pre-commit: ${result.preCommit}, post-commit: ${result.postCommit}`);
32611
32942
  info("Commits of unverified change batches will be blocked in agent worktrees.");
@@ -32618,7 +32949,7 @@ function handleInstall2(argv) {
32618
32949
  function handleUninstall(argv) {
32619
32950
  try {
32620
32951
  if (argv.claude) {
32621
- const result2 = uninstallClaudeGuard(path45.resolve(argv.repo));
32952
+ const result2 = uninstallClaudeGuard(path47.resolve(argv.repo));
32622
32953
  if (result2.removed) {
32623
32954
  success("Claude Code worktree guard removed.");
32624
32955
  } else {
@@ -32626,7 +32957,7 @@ function handleUninstall(argv) {
32626
32957
  }
32627
32958
  return;
32628
32959
  }
32629
- const result = uninstallHooks(path45.resolve(argv.repo));
32960
+ const result = uninstallHooks(path47.resolve(argv.repo));
32630
32961
  if (result.removed) {
32631
32962
  success(`Commit gate removed from ${result.hooksDir}`);
32632
32963
  } else {
@@ -32639,7 +32970,7 @@ function handleUninstall(argv) {
32639
32970
  }
32640
32971
  function handleStatus2(argv) {
32641
32972
  try {
32642
- const status = getHooksStatus(path45.resolve(argv.repo));
32973
+ const status = getHooksStatus(path47.resolve(argv.repo));
32643
32974
  if (argv.json) {
32644
32975
  process.stdout.write(`${JSON.stringify(status, null, 2)}
32645
32976
  `);
@@ -32657,7 +32988,7 @@ function handleStatus2(argv) {
32657
32988
  );
32658
32989
  info(`Effective: ${status.effectiveMode} (in this checkout)`);
32659
32990
  info(
32660
- `Claude guard: ${claudeGuardInstalled(path45.resolve(argv.repo)) ? `installed (${CLAUDE_GUARD_RELATIVE_PATH})` : "not installed"}`
32991
+ `Claude guard: ${claudeGuardInstalled(path47.resolve(argv.repo)) ? `installed (${CLAUDE_GUARD_RELATIVE_PATH})` : "not installed"}`
32661
32992
  );
32662
32993
  } catch (err) {
32663
32994
  error(err instanceof Error ? err.message : String(err));
@@ -32918,10 +33249,10 @@ function assignProp(target, prop, value) {
32918
33249
  configurable: true
32919
33250
  });
32920
33251
  }
32921
- function getElementAtPath(obj, path48) {
32922
- if (!path48)
33252
+ function getElementAtPath(obj, path50) {
33253
+ if (!path50)
32923
33254
  return obj;
32924
- return path48.reduce((acc, key) => acc?.[key], obj);
33255
+ return path50.reduce((acc, key) => acc?.[key], obj);
32925
33256
  }
32926
33257
  function promiseAllObject(promisesObj) {
32927
33258
  const keys = Object.keys(promisesObj);
@@ -33241,11 +33572,11 @@ function aborted(x2, startIndex = 0) {
33241
33572
  }
33242
33573
  return false;
33243
33574
  }
33244
- function prefixIssues(path48, issues) {
33575
+ function prefixIssues(path50, issues) {
33245
33576
  return issues.map((iss) => {
33246
33577
  var _a3;
33247
33578
  (_a3 = iss).path ?? (_a3.path = []);
33248
- iss.path.unshift(path48);
33579
+ iss.path.unshift(path50);
33249
33580
  return iss;
33250
33581
  });
33251
33582
  }
@@ -38568,7 +38899,7 @@ var Protocol = class {
38568
38899
  return;
38569
38900
  }
38570
38901
  const pollInterval = task2.pollInterval ?? this._options?.defaultTaskPollInterval ?? 1e3;
38571
- await new Promise((resolve33) => setTimeout(resolve33, pollInterval));
38902
+ await new Promise((resolve35) => setTimeout(resolve35, pollInterval));
38572
38903
  options?.signal?.throwIfAborted();
38573
38904
  }
38574
38905
  } catch (error3) {
@@ -38585,7 +38916,7 @@ var Protocol = class {
38585
38916
  */
38586
38917
  request(request, resultSchema, options) {
38587
38918
  const { relatedRequestId, resumptionToken, onresumptiontoken, task, relatedTask } = options ?? {};
38588
- return new Promise((resolve33, reject) => {
38919
+ return new Promise((resolve35, reject) => {
38589
38920
  const earlyReject = (error3) => {
38590
38921
  reject(error3);
38591
38922
  };
@@ -38663,7 +38994,7 @@ var Protocol = class {
38663
38994
  if (!parseResult.success) {
38664
38995
  reject(parseResult.error);
38665
38996
  } else {
38666
- resolve33(parseResult.data);
38997
+ resolve35(parseResult.data);
38667
38998
  }
38668
38999
  } catch (error3) {
38669
39000
  reject(error3);
@@ -38924,12 +39255,12 @@ var Protocol = class {
38924
39255
  }
38925
39256
  } catch {
38926
39257
  }
38927
- return new Promise((resolve33, reject) => {
39258
+ return new Promise((resolve35, reject) => {
38928
39259
  if (signal.aborted) {
38929
39260
  reject(new McpError(ErrorCode.InvalidRequest, "Request cancelled"));
38930
39261
  return;
38931
39262
  }
38932
- const timeoutId = setTimeout(resolve33, interval);
39263
+ const timeoutId = setTimeout(resolve35, interval);
38933
39264
  signal.addEventListener("abort", () => {
38934
39265
  clearTimeout(timeoutId);
38935
39266
  reject(new McpError(ErrorCode.InvalidRequest, "Request cancelled"));
@@ -39719,7 +40050,7 @@ var Server = class extends Protocol {
39719
40050
  };
39720
40051
 
39721
40052
  // src/mcp/server.ts
39722
- var path46 = __toESM(require("path"));
40053
+ var path48 = __toESM(require("path"));
39723
40054
 
39724
40055
  // node_modules/@modelcontextprotocol/sdk/dist/esm/server/stdio.js
39725
40056
  var import_node_process2 = __toESM(require("node:process"), 1);
@@ -39802,12 +40133,12 @@ var StdioServerTransport = class {
39802
40133
  this.onclose?.();
39803
40134
  }
39804
40135
  send(message) {
39805
- return new Promise((resolve33) => {
40136
+ return new Promise((resolve35) => {
39806
40137
  const json = serializeMessage(message);
39807
40138
  if (this._stdout.write(json)) {
39808
- resolve33();
40139
+ resolve35();
39809
40140
  } else {
39810
- this._stdout.once("drain", resolve33);
40141
+ this._stdout.once("drain", resolve35);
39811
40142
  }
39812
40143
  });
39813
40144
  }
@@ -39890,10 +40221,7 @@ var LaunchEnvironmentInputSchema = external_exports.object({
39890
40221
  force: external_exports.boolean().default(false).describe(
39891
40222
  "Discard uncommitted changes in a dirty occupied worktree. Requires confirmReplace=true and explicit user approval \u2014 never set autonomously."
39892
40223
  ),
39893
- resume: external_exports.boolean().default(false).describe("Resume a failed or partial launch without creating a new worktree."),
39894
- purpose: external_exports.string().optional().describe(
39895
- "Optional session label stored in the slot registry (shown in status and occupied-slot warnings)."
39896
- )
40224
+ resume: external_exports.boolean().default(false).describe("Resume a failed or partial launch without creating a new worktree.")
39897
40225
  });
39898
40226
  var LaunchEnvironmentOutputSchema = ShellRunOutputSchema.extend({
39899
40227
  previewUrls: external_exports.record(external_exports.string()).optional(),
@@ -39907,8 +40235,7 @@ var LaunchEnvironmentOutputSchema = ShellRunOutputSchema.extend({
39907
40235
  worktreePath: external_exports.string().optional(),
39908
40236
  branch: external_exports.string().optional(),
39909
40237
  dirty: external_exports.boolean().optional(),
39910
- sessionCreatedAt: external_exports.string().optional(),
39911
- purpose: external_exports.string().optional()
40238
+ sessionCreatedAt: external_exports.string().optional()
39912
40239
  }).optional()
39913
40240
  });
39914
40241
  var PreflightEnvironmentInputSchema = external_exports.object({
@@ -40033,10 +40360,6 @@ var HAR_MCP_TOOLS = [
40033
40360
  resume: {
40034
40361
  type: "boolean",
40035
40362
  description: "Resume a failed or partial launch (status failed/starting) without --replace. Preserves worktree and env."
40036
- },
40037
- purpose: {
40038
- type: "string",
40039
- description: "Optional session label stored in the slot registry (shown in status and occupied-slot warnings)."
40040
40363
  }
40041
40364
  },
40042
40365
  ["agentId"]
@@ -40220,7 +40543,6 @@ async function handleMcpToolCall(toolName, args, defaultRepo = ".") {
40220
40543
  confirmReplace: input.confirmReplace,
40221
40544
  force: input.force,
40222
40545
  resume: input.resume,
40223
- purpose: input.purpose,
40224
40546
  capture: true
40225
40547
  });
40226
40548
  const parsed = LaunchEnvironmentOutputSchema.parse(result);
@@ -40360,7 +40682,7 @@ async function handleMcpToolCall(toolName, args, defaultRepo = ".") {
40360
40682
  const input = ControlUpInputSchema.parse({ ...args, repo });
40361
40683
  const result = await startControlAndSync({
40362
40684
  detach: input.detach,
40363
- cwd: path46.resolve(input.repo)
40685
+ cwd: path48.resolve(input.repo)
40364
40686
  });
40365
40687
  if (result.code !== 0) {
40366
40688
  return {
@@ -40423,8 +40745,10 @@ var mcpCommand = {
40423
40745
  };
40424
40746
 
40425
40747
  // src/cli/commands/telemetry.ts
40426
- var fs40 = __toESM(require("fs"));
40427
- var path47 = __toESM(require("path"));
40748
+ var path49 = __toESM(require("path"));
40749
+ init_control_config();
40750
+ init_otel_hooks();
40751
+ init_telemetry_config();
40428
40752
  function printSignals() {
40429
40753
  info("Collected when enabled (local Mission Control only):");
40430
40754
  for (const signal of TELEMETRY_SIGNALS) {
@@ -40448,6 +40772,8 @@ async function handleStatus3(argv) {
40448
40772
  controlApiUrl: apiUrl,
40449
40773
  controlReachable: reachable,
40450
40774
  otelEndpoint: `${apiUrl.replace(/\/$/, "")}/api/otel`,
40775
+ otelHooksHome: getOtelHooksHome(),
40776
+ otelHooksConfig: getOtelHooksConfigPath(),
40451
40777
  signalDescriptions: [...TELEMETRY_SIGNALS]
40452
40778
  };
40453
40779
  if (argv.json) {
@@ -40463,25 +40789,29 @@ async function handleStatus3(argv) {
40463
40789
  );
40464
40790
  info(`Mission Control: ${apiUrl} (${reachable ? "reachable" : "not reachable"})`);
40465
40791
  info(`OTLP endpoint: ${payload.otelEndpoint}`);
40792
+ info(`Hooks home: ${payload.otelHooksHome}`);
40466
40793
  printSignals();
40467
40794
  }
40468
40795
  async function handleOn(argv) {
40469
40796
  header("har telemetry on");
40470
40797
  const preference = writeTelemetryPreference(true, {
40471
40798
  prompts: argv.prompts === true ? true : void 0,
40472
- traces: argv.traces === true ? true : void 0
40799
+ traces: true
40473
40800
  });
40474
- success("Telemetry enabled (opt-out). Agent usage will be stored in local Mission Control.");
40801
+ success("Telemetry enabled. Cursor / Claude / Codex export via opentelemetry-hooks \u2192 Mission Control.");
40475
40802
  info(
40476
40803
  `Signals: metrics=${preference.signals.metrics} logs=${preference.signals.logs} prompts=${preference.signals.prompts} traces=${preference.signals.traces}`
40477
40804
  );
40478
40805
  if (preference.signals.prompts) {
40479
- warn("Prompt/response text will leave the agent machine for local Mission Control storage.");
40806
+ warn("Prompt text will leave the agent machine for local Mission Control (also used as session purpose).");
40480
40807
  }
40481
40808
  printSignals();
40482
40809
  const result = await ensureTelemetryInfrastructure({ startIfNeeded: true });
40483
40810
  if (result.message) success(result.message);
40484
40811
  if (result.warning) warn(result.warning);
40812
+ const hooks = ensureOtelHooks({ setupAgents: true });
40813
+ if (hooks.message) success(hooks.message);
40814
+ if (hooks.warning) warn(hooks.warning);
40485
40815
  if (result.otelReady) {
40486
40816
  info(`OTLP ingest: ${result.apiUrl.replace(/\/$/, "")}/api/otel`);
40487
40817
  info("Usage appears under Mission Control \u2192 Worktrees / Usage. Disable: har telemetry off");
@@ -40490,44 +40820,48 @@ async function handleOn(argv) {
40490
40820
  async function handleOff() {
40491
40821
  header("har telemetry off");
40492
40822
  writeTelemetryPreference(false);
40823
+ try {
40824
+ disableOtelHooksExport();
40825
+ } catch (err) {
40826
+ warn(`Could not refresh hooks config: ${err instanceof Error ? err.message : String(err)}`);
40827
+ }
40493
40828
  success("Telemetry disabled.");
40494
- info("Future launches will not inject OTEL exporters or auto-start Mission Control.");
40495
- info("Existing usage rows in Mission Control are kept. Historical data is not deleted.");
40829
+ info("Hooks stay registered but OTLP export is cleared in ~/.har/otel-hooks/otel_config.json.");
40830
+ info("Mission Control will not auto-start. Existing usage rows are kept.");
40496
40831
  }
40497
40832
  async function handleWriteEnv(argv) {
40498
- const repoPath = path47.resolve(argv.repo);
40833
+ const repoPath = path49.resolve(argv.repo);
40499
40834
  const harnessRoot = resolveHarnessRoot(repoPath);
40500
40835
  const session = readSlotRegistry(harnessRoot, argv.agentId);
40501
40836
  const workDir = argv.workDir ?? session?.workDir ?? repoPath;
40502
40837
  const branch = argv.branch ?? session?.branch;
40503
40838
  const suffix = argv.suffix ?? session?.suffix;
40504
- const purpose = argv.purpose ?? session?.purpose;
40505
40839
  const sessionKey = argv.sessionKey ?? buildSessionKey({
40506
40840
  branch,
40507
40841
  agentId: argv.agentId,
40508
40842
  suffix,
40509
40843
  createdAt: session?.createdAt
40510
40844
  });
40511
- const envFile = argv.envFile ?? path47.join(workDir, `.env.agent.${argv.agentId}`);
40512
- let otelReady = argv.otelReady;
40513
- if (otelReady === void 0 && isTelemetryEnabled()) {
40514
- const ensured = await ensureTelemetryInfrastructure({ startIfNeeded: false });
40515
- otelReady = ensured.otelReady;
40516
- }
40845
+ const envFile = argv.envFile ?? path49.join(workDir, `.env.agent.${argv.agentId}`);
40517
40846
  const attrs = {
40518
40847
  sessionKey,
40519
40848
  agentId: argv.agentId,
40520
40849
  repoPath,
40521
40850
  workDir,
40522
40851
  branch,
40523
- suffix,
40524
- purpose
40852
+ suffix
40525
40853
  };
40526
- appendTelemetryEnvToFile(envFile, attrs, { otelReady: otelReady !== false && isTelemetryEnabled() });
40527
- success(`Wrote telemetry env to ${envFile}`);
40854
+ appendTelemetryEnvToFile(envFile, attrs);
40855
+ if (isTelemetryEnabled()) {
40856
+ ensureOtelHooks({
40857
+ setupAgents: false,
40858
+ resourceAttributes: buildOtelResourceAttributes(attrs)
40859
+ });
40860
+ }
40861
+ success(`Wrote session attribution to ${envFile}`);
40528
40862
  }
40529
40863
  function handlePrintEnv(argv) {
40530
- const repoPath = path47.resolve(argv.repo);
40864
+ const repoPath = path49.resolve(argv.repo);
40531
40865
  const workDir = argv.workDir ?? repoPath;
40532
40866
  const sessionKey = buildSessionKey({
40533
40867
  branch: argv.branch,
@@ -40535,55 +40869,38 @@ function handlePrintEnv(argv) {
40535
40869
  suffix: argv.suffix
40536
40870
  });
40537
40871
  process.stdout.write(
40538
- buildTelemetryEnvBlock(
40539
- {
40540
- sessionKey,
40541
- agentId: argv.agentId,
40542
- repoPath,
40543
- workDir,
40544
- branch: argv.branch,
40545
- suffix: argv.suffix
40546
- },
40547
- { otelReady: argv.otelReady && isTelemetryEnabled() }
40548
- )
40872
+ buildTelemetryEnvBlock({
40873
+ sessionKey,
40874
+ agentId: argv.agentId,
40875
+ repoPath,
40876
+ workDir,
40877
+ branch: argv.branch,
40878
+ suffix: argv.suffix
40879
+ })
40549
40880
  );
40550
40881
  }
40551
- function handleCodexSnippet(argv) {
40552
- const repoPath = path47.resolve(argv.repo);
40553
- const harnessRoot = resolveHarnessRoot(repoPath);
40554
- const session = readSlotRegistry(harnessRoot, argv.agentId);
40555
- const workDir = argv.workDir ?? session?.workDir ?? repoPath;
40556
- const branch = argv.branch ?? session?.branch;
40557
- const suffix = argv.suffix ?? session?.suffix;
40558
- const sessionKey = buildSessionKey({
40559
- branch,
40560
- agentId: argv.agentId,
40561
- suffix,
40562
- createdAt: session?.createdAt
40563
- });
40564
- const snippet = buildCodexOtelSnippet({
40565
- sessionKey,
40566
- agentId: argv.agentId,
40567
- repoPath,
40568
- workDir,
40569
- branch,
40570
- suffix,
40571
- purpose: session?.purpose
40572
- });
40573
- if (argv.write) {
40574
- const outDir = path47.join(harnessRoot, ".har", "telemetry");
40575
- fs40.mkdirSync(outDir, { recursive: true });
40576
- const outPath = path47.join(outDir, "codex.otel.toml.snippet");
40577
- fs40.writeFileSync(outPath, snippet);
40578
- success(`Wrote ${outPath}`);
40579
- info("Merge the [otel] table into ~/.codex/config.toml manually.");
40580
- return;
40882
+ async function handleInstallHooks() {
40883
+ header("har telemetry install-hooks");
40884
+ if (!isTelemetryEnabled()) {
40885
+ warn("Telemetry is off \u2014 enabling preference so hooks can export.");
40886
+ writeTelemetryPreference(true, { traces: true });
40887
+ }
40888
+ const result = await ensureTelemetryInfrastructure({ startIfNeeded: true });
40889
+ if (result.message) success(result.message);
40890
+ if (result.warning) warn(result.warning);
40891
+ const hooks = ensureOtelHooks({ setupAgents: true });
40892
+ if (hooks.ok) {
40893
+ success(hooks.message ?? "opentelemetry-hooks installed and agents registered");
40894
+ info(`Config: ${hooks.configPath}`);
40895
+ info(`Wrapper: ${hooks.wrapperPath}`);
40896
+ } else {
40897
+ error(hooks.warning ?? "Failed to install opentelemetry-hooks");
40898
+ process.exitCode = 1;
40581
40899
  }
40582
- process.stdout.write(snippet);
40583
40900
  }
40584
40901
  var telemetryCommand = {
40585
40902
  command: "telemetry <subcommand>",
40586
- describe: "Agent usage telemetry (Claude Code / Codex \u2192 Mission Control)",
40903
+ describe: "Agent usage telemetry (Cursor / Claude / Codex via opentelemetry-hooks \u2192 Mission Control)",
40587
40904
  builder: (yargs) => yargs.command(
40588
40905
  "status",
40589
40906
  "Show telemetry preference and Mission Control reachability",
@@ -40593,34 +40910,40 @@ var telemetryCommand = {
40593
40910
  }
40594
40911
  ).command(
40595
40912
  "on",
40596
- "Enable telemetry (default) and ensure Mission Control is running",
40913
+ "Enable telemetry, ensure Mission Control, install/configure opentelemetry-hooks",
40597
40914
  (y2) => y2.option("prompts", {
40598
40915
  type: "boolean",
40599
40916
  default: false,
40600
- describe: "Opt in to shipping user/assistant prompt text to Mission Control"
40601
- }).option("traces", {
40602
- type: "boolean",
40603
- default: false,
40604
- describe: "Opt in to thin OTEL traces ingest (Claude beta)"
40917
+ describe: "Opt in to shipping user prompt text (also fills Mission Control purpose)"
40605
40918
  }),
40606
40919
  (argv) => {
40607
40920
  void handleOn({
40608
- prompts: argv.prompts,
40609
- traces: argv.traces
40921
+ prompts: argv.prompts
40610
40922
  });
40611
40923
  }
40612
40924
  ).command(
40613
40925
  "off",
40614
- "Disable telemetry (no OTEL injection, no MC auto-start)",
40926
+ "Disable telemetry (clear hooks OTLP endpoint, no MC auto-start)",
40615
40927
  () => {
40616
40928
  },
40617
40929
  () => {
40618
40930
  void handleOff();
40619
40931
  }
40932
+ ).command(
40933
+ "install-hooks",
40934
+ "Install opentelemetry-hooks and register Cursor / Claude / Codex",
40935
+ () => {
40936
+ },
40937
+ () => {
40938
+ void handleInstallHooks().catch((err) => {
40939
+ error(err instanceof Error ? err.message : String(err));
40940
+ process.exitCode = 1;
40941
+ });
40942
+ }
40620
40943
  ).command(
40621
40944
  "write-env",
40622
- "Append session + OTEL env vars to .env.agent.<id>",
40623
- (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" }),
40945
+ "Append HAR session attribution to .env.agent.<id> and refresh hooks config",
40946
+ (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("session-key", { type: "string" }),
40624
40947
  (argv) => handleWriteEnv({
40625
40948
  agentId: argv["agent-id"],
40626
40949
  repo: argv.repo,
@@ -40628,42 +40951,23 @@ var telemetryCommand = {
40628
40951
  workDir: argv["work-dir"],
40629
40952
  branch: argv.branch,
40630
40953
  suffix: argv.suffix,
40631
- purpose: argv.purpose,
40632
- sessionKey: argv["session-key"],
40633
- otelReady: argv["otel-ready"]
40954
+ sessionKey: argv["session-key"]
40634
40955
  }).catch((err) => {
40635
40956
  error(err instanceof Error ? err.message : String(err));
40636
40957
  process.exitCode = 1;
40637
40958
  })
40638
40959
  ).command(
40639
40960
  "print-env",
40640
- "Print telemetry env block to stdout",
40641
- (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 }),
40961
+ "Print session attribution env block to stdout",
40962
+ (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" }),
40642
40963
  (argv) => handlePrintEnv({
40643
40964
  agentId: argv["agent-id"],
40644
40965
  repo: argv.repo,
40645
40966
  workDir: argv["work-dir"],
40646
40967
  branch: argv.branch,
40647
- suffix: argv.suffix,
40648
- otelReady: argv["otel-ready"]
40649
- })
40650
- ).command(
40651
- "codex-snippet",
40652
- "Print or write a Codex [otel] config snippet",
40653
- (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", {
40654
- type: "boolean",
40655
- default: false,
40656
- describe: "Write to .har/telemetry/codex.otel.toml.snippet"
40657
- }),
40658
- (argv) => handleCodexSnippet({
40659
- agentId: argv["agent-id"],
40660
- repo: argv.repo,
40661
- workDir: argv["work-dir"],
40662
- branch: argv.branch,
40663
- suffix: argv.suffix,
40664
- write: argv.write
40968
+ suffix: argv.suffix
40665
40969
  })
40666
- ).demandCommand(1, "Please specify a subcommand: status, on, off, write-env, print-env, codex-snippet"),
40970
+ ).demandCommand(1, "Please specify a subcommand: status, on, off, install-hooks, write-env, print-env"),
40667
40971
  handler: () => {
40668
40972
  }
40669
40973
  };