@devicelink/dlink 0.2.2 → 0.3.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.
Files changed (4) hide show
  1. package/README.md +24 -37
  2. package/index.js +343 -161
  3. package/index.js.map +4 -4
  4. package/package.json +1 -1
package/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // agent/src/cli/core/engine.ts
4
- import crypto4 from "node:crypto";
4
+ import crypto5 from "node:crypto";
5
5
 
6
6
  // agent/src/cli/core/types.ts
7
7
  var RuntimeFlags = class {
@@ -48,8 +48,8 @@ var Deferred = class {
48
48
  };
49
49
  this.rejectFn = () => {
50
50
  };
51
- this.promise = new Promise((resolve, reject) => {
52
- this.resolveFn = resolve;
51
+ this.promise = new Promise((resolve3, reject) => {
52
+ this.resolveFn = resolve3;
53
53
  this.rejectFn = reject;
54
54
  });
55
55
  }
@@ -272,8 +272,8 @@ var ConfigurationContext = class {
272
272
  };
273
273
 
274
274
  // agent/src/cli/utils/config.builder.ts
275
- import path2 from "node:path";
276
- import os2 from "node:os";
275
+ import path4 from "node:path";
276
+ import os3 from "node:os";
277
277
  import fs2 from "node:fs";
278
278
  import dotenv from "dotenv";
279
279
  import get2 from "lodash/get.js";
@@ -524,8 +524,8 @@ function canonicalizePath(input) {
524
524
  out = out.toLowerCase();
525
525
  return out;
526
526
  }
527
- function getDirectoryTag(path11, groups = [3, 3, 3, 3]) {
528
- const canonical = canonicalizePath(path11);
527
+ function getDirectoryTag(path13, groups = [3, 3, 3, 3]) {
528
+ const canonical = canonicalizePath(path13);
529
529
  const hash = crypto3.createHash("sha256").update(canonical, "utf8").digest("base64url");
530
530
  const clean = hash.replace(/[^a-zA-Z0-9]/g, "").toLowerCase();
531
531
  const needed = groups.reduce((a, b) => a + b, 0);
@@ -533,8 +533,8 @@ function getDirectoryTag(path11, groups = [3, 3, 3, 3]) {
533
533
  let i = 0;
534
534
  return groups.map((len) => padded.slice(i, i += len)).join("-");
535
535
  }
536
- function pathToUuidLikeSha256(path11) {
537
- const canonical = canonicalizePath(path11);
536
+ function pathToUuidLikeSha256(path13) {
537
+ const canonical = canonicalizePath(path13);
538
538
  const hex = crypto3.createHash("sha256").update(canonical, "utf8").digest("hex");
539
539
  const h = hex.slice(0, 32);
540
540
  const part1 = h.slice(0, 8);
@@ -545,6 +545,118 @@ function pathToUuidLikeSha256(path11) {
545
545
  return `${part1}-${part2}-${part3}-${part4}-${part5}`;
546
546
  }
547
547
 
548
+ // common/src/lib/utils/crypto.utils.ts
549
+ import * as crypto4 from "node:crypto";
550
+ function fingerprintFromPublicKeyPem(publicKeyPem) {
551
+ const keyObj = crypto4.createPublicKey(publicKeyPem);
552
+ if (keyObj.asymmetricKeyType !== "ed25519") {
553
+ throw new Error(`Unexpected key type: ${keyObj.asymmetricKeyType}`);
554
+ }
555
+ const spkiDer = keyObj.export({ format: "der", type: "spki" });
556
+ const hash = crypto4.createHash("sha256").update(spkiDer).digest("base64url");
557
+ return hash.slice(0, 16).toUpperCase();
558
+ }
559
+ function signConnect(params) {
560
+ const payload = Buffer.from(`${params.deviceId}|${params.ts}|${params.nonce}`, "utf8");
561
+ const key = crypto4.createPrivateKey(params.privateKeyPem);
562
+ const sig = crypto4.sign(null, payload, key);
563
+ return sig.toString("base64");
564
+ }
565
+ function signRefresh(params) {
566
+ const hash = crypto4.createHash("sha256").update(params.refreshToken, "utf8").digest("hex");
567
+ const payload = Buffer.from(`${params.deviceId}|${params.ts}|${params.nonce}|${hash}`, "utf8");
568
+ const key = crypto4.createPrivateKey(params.privateKeyPem);
569
+ const sig = crypto4.sign(null, payload, key);
570
+ return sig.toString("base64");
571
+ }
572
+
573
+ // common/src/lib/utils/async.utils.ts
574
+ function sleep(ms) {
575
+ return new Promise((resolve3) => setTimeout(resolve3, ms));
576
+ }
577
+
578
+ // common/src/lib/utils/path-display.utils.ts
579
+ import * as path2 from "node:path";
580
+ import * as os2 from "node:os";
581
+ function normalizeSlashes(s) {
582
+ return s.replace(/\\/g, "/");
583
+ }
584
+ function looksLikeWindowsDrivePath(s) {
585
+ return /^[A-Za-z]:[\\/]/.test(s) || /^[A-Za-z]:$/.test(s.trim());
586
+ }
587
+ function normalizeWindowsSeparatorsForMatch(s) {
588
+ if (!looksLikeWindowsDrivePath(s))
589
+ return s;
590
+ return s.replace(/\//g, "\\");
591
+ }
592
+ function toTildeHomePath(absolutePath) {
593
+ if (absolutePath == null)
594
+ return void 0;
595
+ const raw = String(absolutePath).trim();
596
+ if (raw === "")
597
+ return void 0;
598
+ if (raw.startsWith("~")) {
599
+ return normalizeSlashes(raw);
600
+ }
601
+ if (!looksLikeWindowsDrivePath(raw)) {
602
+ try {
603
+ const abs = path2.resolve(raw);
604
+ const home = path2.resolve(os2.homedir());
605
+ const rel = path2.relative(home, abs);
606
+ if (!rel.startsWith("..") && !path2.isAbsolute(rel)) {
607
+ if (rel === "")
608
+ return "~";
609
+ return "~/" + normalizeSlashes(rel);
610
+ }
611
+ } catch {
612
+ }
613
+ }
614
+ const win = normalizeWindowsSeparatorsForMatch(raw);
615
+ const winUsers = win.match(/^([A-Za-z]:)\\(?:[Uu][Ss][Ee][Rr][Ss])\\([^\\]+)(\\.*)?$/);
616
+ if (winUsers) {
617
+ const rest = (winUsers[3] ?? "").replace(/\\/g, "/");
618
+ return rest ? "~" + rest : "~";
619
+ }
620
+ const wsl = raw.match(/^\/mnt\/[a-z]\/(?:[Uu][Ss][Ee][Rr][Ss])\/[^/]+(\/.*)?$/i);
621
+ if (wsl) {
622
+ const rest = wsl[1] ?? "";
623
+ return rest ? "~" + normalizeSlashes(rest) : "~";
624
+ }
625
+ const mac = raw.match(/^\/Users\/[^/]+(\/.*)?$/);
626
+ if (mac) {
627
+ const rest = mac[1] ?? "";
628
+ return rest ? "~" + normalizeSlashes(rest) : "~";
629
+ }
630
+ const lin = raw.match(/^\/home\/[^/]+(\/.*)?$/);
631
+ if (lin) {
632
+ const rest = lin[1] ?? "";
633
+ return rest ? "~" + normalizeSlashes(rest) : "~";
634
+ }
635
+ const fbsd = raw.match(/^\/usr\/home\/[^/]+(\/.*)?$/);
636
+ if (fbsd) {
637
+ const rest = fbsd[1] ?? "";
638
+ return rest ? "~" + normalizeSlashes(rest) : "~";
639
+ }
640
+ const exp = raw.match(/^\/export\/home\/[^/]+(\/.*)?$/);
641
+ if (exp) {
642
+ const rest = exp[1] ?? "";
643
+ return rest ? "~" + normalizeSlashes(rest) : "~";
644
+ }
645
+ try {
646
+ return path2.basename(path2.resolve(raw));
647
+ } catch {
648
+ return raw.split(/[/\\]/).pop() ?? raw;
649
+ }
650
+ }
651
+
652
+ // common/src/lib/utils/path.utils.ts
653
+ import * as path3 from "node:path";
654
+ var isCaseInsensitiveFS = process.platform === "win32";
655
+ function pathDedupeKey(p) {
656
+ const resolved = path3.resolve(p);
657
+ return isCaseInsensitiveFS ? resolved.toLowerCase() : resolved;
658
+ }
659
+
548
660
  // agent/src/cli/utils/config.builder.ts
549
661
  var DEFAULT_CONFIG = {
550
662
  version: 1,
@@ -576,9 +688,9 @@ var ConfigLoader = class {
576
688
  loadProductionEnv() {
577
689
  const candidates = [
578
690
  // npm install: .env.production alongside index.js
579
- path2.resolve(path2.dirname(process.argv[1] ?? "."), ".env.production"),
691
+ path4.resolve(path4.dirname(process.argv[1] ?? "."), ".env.production"),
580
692
  // monorepo dev: agent/.env.production
581
- path2.resolve(process.cwd(), ".env.production")
693
+ path4.resolve(process.cwd(), ".env.production")
582
694
  ];
583
695
  for (const candidate of candidates) {
584
696
  try {
@@ -626,7 +738,7 @@ var ConfigLoader = class {
626
738
  withConfigFile(filename) {
627
739
  try {
628
740
  const configFolder = this.getApplicationConfigFolder();
629
- const configPath = path2.join(configFolder, filename);
741
+ const configPath = path4.join(configFolder, filename);
630
742
  if (fs2.existsSync(configPath)) {
631
743
  const raw = fs2.readFileSync(configPath, "utf8");
632
744
  const parsed = JSON.parse(raw);
@@ -664,36 +776,49 @@ var ConfigLoader = class {
664
776
  return this.argv?.bridgeWsUrl ?? this.safeGetEnvValue("DLINK_BRIDGE_URL") ?? this.safeGetConfigValue("bridgeWsUrl") ?? this.processEnv["DLINK_BRIDGE_URL"] ?? this.productionEnv["DLINK_BRIDGE_URL"] ?? void 0;
665
777
  }
666
778
  getDirectories() {
779
+ const collected = [];
780
+ collected.push(this.createDirectoryFromPath(process.cwd()));
667
781
  if (this.argv?.rootPath) {
668
782
  try {
669
- return this.parseDirectoriesFromString(this.argv.rootPath);
783
+ collected.push(...this.parseDirectoriesFromString(this.argv.rootPath));
670
784
  } catch {
671
- return [this.createDirectoryFromPath(this.argv.rootPath)];
785
+ collected.push(this.createDirectoryFromPath(this.argv.rootPath));
672
786
  }
673
787
  }
674
788
  const envDirs = this.safeGetEnvValue("DLINK_DIRECTORIES") ?? this.safeGetEnvValue("DLINK_ROOTS");
675
789
  if (envDirs) {
676
790
  try {
677
- return this.parseDirectoriesFromString(envDirs);
791
+ collected.push(...this.parseDirectoriesFromString(envDirs));
678
792
  } catch {
679
793
  }
680
794
  }
681
795
  const configDirs = this.safeGetConfigValue("directories") ?? this.safeGetConfigValue("roots");
682
796
  if (configDirs) {
683
797
  try {
684
- return this.parseDirectoriesFromValue(configDirs);
798
+ collected.push(...this.parseDirectoriesFromValue(configDirs));
685
799
  } catch {
686
800
  }
687
801
  }
688
802
  const processDirs = this.processEnv["DLINK_DIRECTORIES"] ?? this.processEnv["DLINK_ROOTS"];
689
803
  if (processDirs) {
690
804
  try {
691
- return this.parseDirectoriesFromString(processDirs);
805
+ collected.push(...this.parseDirectoriesFromString(processDirs));
692
806
  } catch {
693
807
  }
694
808
  }
695
- const cwd = process.cwd();
696
- return [this.createDirectoryFromPath(cwd)];
809
+ return this.deduplicateDirectories(collected);
810
+ }
811
+ deduplicateDirectories(dirs) {
812
+ const seen = /* @__PURE__ */ new Set();
813
+ const result = [];
814
+ for (const dir of dirs) {
815
+ const key = pathDedupeKey(dir.path);
816
+ if (seen.has(key))
817
+ continue;
818
+ seen.add(key);
819
+ result.push(dir);
820
+ }
821
+ return result;
697
822
  }
698
823
  getDevice() {
699
824
  const deviceId = this.getDeviceId();
@@ -703,7 +828,7 @@ var ConfigLoader = class {
703
828
  }
704
829
  let deviceName = this.getDeviceName();
705
830
  if (!deviceName) {
706
- deviceName = os2.hostname() || "Unknown Device";
831
+ deviceName = os3.hostname() || "Unknown Device";
707
832
  }
708
833
  const deviceTag = this.getDeviceTag();
709
834
  const keyBuilder = new KeyBuilder({
@@ -767,7 +892,7 @@ var ConfigLoader = class {
767
892
  const processName = this.processEnv["DLINK_DEVICE_NAME"];
768
893
  if (processName && !isEmpty2(processName))
769
894
  return processName;
770
- const hostname = os2.hostname();
895
+ const hostname = os3.hostname();
771
896
  if (hostname && !isEmpty2(hostname))
772
897
  return hostname;
773
898
  return void 0;
@@ -842,7 +967,7 @@ var ConfigLoader = class {
842
967
  const directoryPath = get2(obj, "path");
843
968
  const name = defaultTo(
844
969
  get2(obj, "name"),
845
- directoryPath ? path2.basename(directoryPath) : ""
970
+ directoryPath ? path4.basename(directoryPath) : ""
846
971
  );
847
972
  if (!isEmpty2(id) && !isEmpty2(directoryPath)) {
848
973
  const tag = getDirectoryTag(directoryPath);
@@ -857,18 +982,18 @@ var ConfigLoader = class {
857
982
  return null;
858
983
  }
859
984
  createDirectoryFromPath(directoryPath) {
860
- const normalizedPath = path2.resolve(directoryPath);
985
+ const normalizedPath = path4.resolve(directoryPath);
861
986
  const id = pathToUuidLikeSha256(normalizedPath);
862
- const name = path2.basename(normalizedPath);
987
+ const name = path4.basename(normalizedPath);
863
988
  const tag = getDirectoryTag(normalizedPath);
864
989
  return { id, name, path: normalizedPath, tag };
865
990
  }
866
991
  getApplicationConfigFolder() {
867
992
  const appData = defaultTo(
868
993
  this.processEnv["APPDATA"],
869
- path2.join(os2.homedir(), "AppData", "Roaming")
994
+ path4.join(os3.homedir(), "AppData", "Roaming")
870
995
  );
871
- return path2.join(appData, "DeviceLink");
996
+ return path4.join(appData, "DeviceLink");
872
997
  }
873
998
  };
874
999
 
@@ -962,6 +1087,13 @@ var Engine2 = class {
962
1087
  getBus() {
963
1088
  return this.bus;
964
1089
  }
1090
+ hasRunningJobs() {
1091
+ for (const [, count] of this.laneActive) {
1092
+ if (count > 0)
1093
+ return true;
1094
+ }
1095
+ return false;
1096
+ }
965
1097
  register(jobType) {
966
1098
  const name = jobType.options.name;
967
1099
  if (this.registry.has(name)) {
@@ -1124,7 +1256,7 @@ var Engine2 = class {
1124
1256
  await this.loopOnce();
1125
1257
  } catch (err) {
1126
1258
  this.notifier.error(`Engine loop error: ${String(err)}`, { scope: "system" /* System */ });
1127
- await new Promise((resolve) => setTimeout(resolve, 250));
1259
+ await new Promise((resolve3) => setTimeout(resolve3, 250));
1128
1260
  }
1129
1261
  }
1130
1262
  }
@@ -1407,12 +1539,12 @@ var Engine2 = class {
1407
1539
  this.wakeFn();
1408
1540
  }
1409
1541
  resetWake() {
1410
- return new Promise((resolve) => {
1411
- this.wakeFn = resolve;
1542
+ return new Promise((resolve3) => {
1543
+ this.wakeFn = resolve3;
1412
1544
  });
1413
1545
  }
1414
1546
  newId() {
1415
- return crypto4.randomBytes(8).toString("hex");
1547
+ return crypto5.randomBytes(8).toString("hex");
1416
1548
  }
1417
1549
  };
1418
1550
  function backoffMs(attempt) {
@@ -1455,10 +1587,10 @@ var EventBus = class {
1455
1587
  };
1456
1588
 
1457
1589
  // agent/src/storage/index.ts
1458
- import * as crypto5 from "node:crypto";
1590
+ import * as crypto6 from "node:crypto";
1459
1591
  import * as fs3 from "node:fs/promises";
1460
- import * as path3 from "node:path";
1461
- import * as os3 from "node:os";
1592
+ import * as path5 from "node:path";
1593
+ import * as os4 from "node:os";
1462
1594
  import defaultTo2 from "lodash/defaultTo.js";
1463
1595
  var SALT = "DeviceLink-SecretStorage-v1";
1464
1596
  var IV_LENGTH = 12;
@@ -1468,12 +1600,12 @@ var SECRETS_FILENAME = "secrets.enc.json";
1468
1600
  function getSecretsPath() {
1469
1601
  const appData = defaultTo2(
1470
1602
  process.env["APPDATA"],
1471
- path3.join(os3.homedir(), "AppData", "Roaming")
1603
+ path5.join(os4.homedir(), "AppData", "Roaming")
1472
1604
  );
1473
- return path3.join(appData, "DeviceLink", SECRETS_FILENAME);
1605
+ return path5.join(appData, "DeviceLink", SECRETS_FILENAME);
1474
1606
  }
1475
1607
  function deriveKey(machineId) {
1476
- return crypto5.pbkdf2Sync(
1608
+ return crypto6.pbkdf2Sync(
1477
1609
  machineId,
1478
1610
  SALT,
1479
1611
  1e5,
@@ -1503,7 +1635,7 @@ var EncryptedFileStorage = class {
1503
1635
  this.loaded = true;
1504
1636
  }
1505
1637
  async save() {
1506
- await fs3.mkdir(path3.dirname(this.secretsPath), { recursive: true });
1638
+ await fs3.mkdir(path5.dirname(this.secretsPath), { recursive: true });
1507
1639
  const plain = JSON.stringify(this.cache);
1508
1640
  const encrypted = this.encrypt(plain);
1509
1641
  const tmp = this.secretsPath + ".tmp";
@@ -1511,8 +1643,8 @@ var EncryptedFileStorage = class {
1511
1643
  await fs3.rename(tmp, this.secretsPath);
1512
1644
  }
1513
1645
  encrypt(plain) {
1514
- const iv = crypto5.randomBytes(IV_LENGTH);
1515
- const cipher = crypto5.createCipheriv("aes-256-gcm", this.key, iv, {
1646
+ const iv = crypto6.randomBytes(IV_LENGTH);
1647
+ const cipher = crypto6.createCipheriv("aes-256-gcm", this.key, iv, {
1516
1648
  authTagLength: TAG_LENGTH
1517
1649
  });
1518
1650
  const enc = Buffer.concat([
@@ -1527,7 +1659,7 @@ var EncryptedFileStorage = class {
1527
1659
  const iv = buf.subarray(0, IV_LENGTH);
1528
1660
  const tag = buf.subarray(IV_LENGTH, IV_LENGTH + TAG_LENGTH);
1529
1661
  const enc = buf.subarray(IV_LENGTH + TAG_LENGTH);
1530
- const decipher = crypto5.createDecipheriv("aes-256-gcm", this.key, iv, {
1662
+ const decipher = crypto6.createDecipheriv("aes-256-gcm", this.key, iv, {
1531
1663
  authTagLength: TAG_LENGTH
1532
1664
  });
1533
1665
  decipher.setAuthTag(tag);
@@ -1588,7 +1720,7 @@ var LoadConfigJob = class extends JobType {
1588
1720
  );
1589
1721
  }
1590
1722
  async run(input, ctx) {
1591
- ctx.notifier.info("Loading configuration...");
1723
+ ctx.notifier.info("Loading configuration...", { scope: "system" /* System */ });
1592
1724
  const configLoader = ctx.engine.configurationContext.loader;
1593
1725
  const refreshRes = await ctx.engine.secrets.get("devicePrivateKey");
1594
1726
  configLoader.setPrivateKeyCache(refreshRes.ok && refreshRes.value ? refreshRes.value : void 0);
@@ -1611,13 +1743,14 @@ var LoadConfigJob = class extends JobType {
1611
1743
  return new LoadConfigOutput(true, config);
1612
1744
  }
1613
1745
  updateUi(config, ctx) {
1614
- ctx.notifier.info(`Control base URL: ${config.controlBaseUrl}`);
1615
- ctx.notifier.info(`Bridge WebSocket URL: ${config.bridgeWsUrl}`);
1616
- ctx.notifier.info(`Directories: ${config.directories.map((directory) => directory.path).join(", ")}`);
1617
- ctx.notifier.info(`Device: ${config.device?.id}`);
1618
- ctx.notifier.info(`Device name: ${config.device?.name}`);
1619
- ctx.notifier.info(`Device tag: ${config.device?.tag}`);
1620
- ctx.notifier.info(`Device keys public: ${config.device?.keys?.public ? "***" : "(none)"}`);
1746
+ const sys = { scope: "system" /* System */ };
1747
+ ctx.notifier.info(`Control base URL: ${config.controlBaseUrl}`, sys);
1748
+ ctx.notifier.info(`Bridge WebSocket URL: ${config.bridgeWsUrl}`, sys);
1749
+ ctx.notifier.info(`Directories: ${config.directories.map((directory) => directory.path).join(", ")}`, sys);
1750
+ ctx.notifier.info(`Device: ${config.device?.id}`, sys);
1751
+ ctx.notifier.info(`Device name: ${config.device?.name}`, sys);
1752
+ ctx.notifier.info(`Device tag: ${config.device?.tag}`, sys);
1753
+ ctx.notifier.info(`Device keys public: ${config.device?.keys?.public ? "***" : "(none)"}`, sys);
1621
1754
  }
1622
1755
  };
1623
1756
  var loadConfigJob = new LoadConfigJob();
@@ -1647,7 +1780,7 @@ var ConsoleCommandJob = class extends JobType {
1647
1780
  }
1648
1781
  async run(input, ctx) {
1649
1782
  ctx.notifier.info(`Executing console command: ${input.kind}`);
1650
- await new Promise((resolve) => setTimeout(resolve, 2e3));
1783
+ await new Promise((resolve3) => setTimeout(resolve3, 2e3));
1651
1784
  ctx.notifier.info(`Console command executed: ${input.kind}`);
1652
1785
  ctx.notifier.success(`Console command executed: ${input.kind}`);
1653
1786
  return new ConsoleCommandOutput(true);
@@ -1677,7 +1810,7 @@ var ContactServerJob = class extends JobType {
1677
1810
  }
1678
1811
  async run(input, ctx) {
1679
1812
  ctx.notifier.info(`contact server mode=${input.mode}`, { scope: "system" /* System */ });
1680
- await new Promise((resolve) => setTimeout(resolve, 200));
1813
+ await new Promise((resolve3) => setTimeout(resolve3, 200));
1681
1814
  return new ContactServerOutput(true);
1682
1815
  }
1683
1816
  };
@@ -1705,7 +1838,7 @@ var SendServerResultJob = class extends JobType {
1705
1838
  }
1706
1839
  async run(input, ctx) {
1707
1840
  ctx.notifier.info(`send result requestId=${input.requestId}`, { scope: "system" /* System */ });
1708
- await new Promise((resolve) => setTimeout(resolve, 10));
1841
+ await new Promise((resolve3) => setTimeout(resolve3, 10));
1709
1842
  return new SendServerResultOutput(true);
1710
1843
  }
1711
1844
  };
@@ -1724,8 +1857,8 @@ var ApiCall = class {
1724
1857
  return this.handler(this.context, input);
1725
1858
  }
1726
1859
  };
1727
- async function fetchJson(context, path11, init) {
1728
- const url = `${context.configuration.controlBaseUrl}${path11}`;
1860
+ async function fetchJson(context, path13, init) {
1861
+ const url = `${context.configuration.controlBaseUrl}${path13}`;
1729
1862
  const res = await fetch(url, init);
1730
1863
  if (!res.ok) {
1731
1864
  const text = await res.text().catch(() => "");
@@ -1982,36 +2115,6 @@ var Api = class {
1982
2115
  }
1983
2116
  };
1984
2117
 
1985
- // common/src/lib/utils/crypto.utils.ts
1986
- import * as crypto6 from "node:crypto";
1987
- function fingerprintFromPublicKeyPem(publicKeyPem) {
1988
- const keyObj = crypto6.createPublicKey(publicKeyPem);
1989
- if (keyObj.asymmetricKeyType !== "ed25519") {
1990
- throw new Error(`Unexpected key type: ${keyObj.asymmetricKeyType}`);
1991
- }
1992
- const spkiDer = keyObj.export({ format: "der", type: "spki" });
1993
- const hash = crypto6.createHash("sha256").update(spkiDer).digest("base64url");
1994
- return hash.slice(0, 16).toUpperCase();
1995
- }
1996
- function signConnect(params) {
1997
- const payload = Buffer.from(`${params.deviceId}|${params.ts}|${params.nonce}`, "utf8");
1998
- const key = crypto6.createPrivateKey(params.privateKeyPem);
1999
- const sig = crypto6.sign(null, payload, key);
2000
- return sig.toString("base64");
2001
- }
2002
- function signRefresh(params) {
2003
- const hash = crypto6.createHash("sha256").update(params.refreshToken, "utf8").digest("hex");
2004
- const payload = Buffer.from(`${params.deviceId}|${params.ts}|${params.nonce}|${hash}`, "utf8");
2005
- const key = crypto6.createPrivateKey(params.privateKeyPem);
2006
- const sig = crypto6.sign(null, payload, key);
2007
- return sig.toString("base64");
2008
- }
2009
-
2010
- // common/src/lib/utils/async.utils.ts
2011
- function sleep(ms) {
2012
- return new Promise((resolve) => setTimeout(resolve, ms));
2013
- }
2014
-
2015
2118
  // agent/src/cli/jobs/refreshTokens.ts
2016
2119
  function isWrappedOk(x) {
2017
2120
  if (typeof x !== "object" || x === null)
@@ -3312,6 +3415,9 @@ var ConsoleOrchestrator = class {
3312
3415
  ctx.bus.emit("runtime:debug:changed", { enabled: false });
3313
3416
  ctx.notifier.success("Debug mode disabled");
3314
3417
  break;
3418
+ case "replace" /* Replace */:
3419
+ ctx.bus.emit("auth:session_conflict:resolve", { choice: "USE_NEW" });
3420
+ break;
3315
3421
  default:
3316
3422
  ctx.notifier.warn(`Command not implemented: ${input.kind}`);
3317
3423
  break;
@@ -3361,6 +3467,9 @@ var ConsoleOrchestrator = class {
3361
3467
  if (cmd === "doctor") {
3362
3468
  return { handled: true, kind: "doctor" /* Doctor */ };
3363
3469
  }
3470
+ if (cmd === "replace" || cmd === "yes" || cmd === "y") {
3471
+ return { handled: true, kind: "replace" /* Replace */ };
3472
+ }
3364
3473
  }
3365
3474
  return { handled: false };
3366
3475
  });
@@ -3564,7 +3673,7 @@ var RecoverableSocket = class {
3564
3673
  }
3565
3674
  async connectWebSocketOnce() {
3566
3675
  const myConnId = ++this.connId;
3567
- return new Promise((resolve, reject) => {
3676
+ return new Promise((resolve3, reject) => {
3568
3677
  if (this.stopped) {
3569
3678
  reject(new Error("Socket is stopped"));
3570
3679
  return;
@@ -3582,7 +3691,7 @@ var RecoverableSocket = class {
3582
3691
  return;
3583
3692
  settled = true;
3584
3693
  cleanup();
3585
- resolve();
3694
+ resolve3();
3586
3695
  };
3587
3696
  const settleReject = (error, ws2) => {
3588
3697
  if (settled)
@@ -3810,7 +3919,6 @@ var WebSocketOrchestrator = class {
3810
3919
  };
3811
3920
  return {
3812
3921
  "config:cache:validated": async (_, ctx) => initSocket("config:cache:validated", ctx),
3813
- "auth:signin:completed": async (_, ctx) => initSocket("auth:signin:completed", ctx),
3814
3922
  "auth:signout:requested": async (_, ctx) => {
3815
3923
  ctx.notifier.info("WS: sign-out requested -> disconnecting");
3816
3924
  this.stopped = true;
@@ -3829,7 +3937,10 @@ var WebSocketOrchestrator = class {
3829
3937
  ctx.notifier.info("WS: reconnect requested by auth", { scope: "system" /* System */ });
3830
3938
  this.retryAttempt = 0;
3831
3939
  this.clearCountdown();
3832
- this.socket?.disconnect(false);
3940
+ if (this.socket) {
3941
+ this.socket.stop();
3942
+ this.socket = null;
3943
+ }
3833
3944
  this.ensureStarted(ctx, "auth:ws:reconnect");
3834
3945
  },
3835
3946
  // the only sender
@@ -3848,6 +3959,11 @@ var WebSocketOrchestrator = class {
3848
3959
  ctx.notifier.info(`WS: connect loop already running (reason=${reason})`, { scope: "system" /* System */ });
3849
3960
  return;
3850
3961
  }
3962
+ const ws = this.socket?.getSocket();
3963
+ if (ws && ws.readyState <= 1) {
3964
+ ctx.notifier.info(`WS: already connected/connecting, skipping (reason=${reason})`, { scope: "system" /* System */ });
3965
+ return;
3966
+ }
3851
3967
  this.connectLoopInFlight = this.startOrJoinConnectLoop(ctx, reason).finally(() => {
3852
3968
  this.connectLoopInFlight = null;
3853
3969
  });
@@ -3862,32 +3978,47 @@ var WebSocketOrchestrator = class {
3862
3978
  this.lastUrl = url;
3863
3979
  ctx.notifier.info(`WS: ensure connection (reason=${reason}) url=${url}`, { scope: "system" /* System */ });
3864
3980
  if (!this.socket) {
3865
- this.socket = new RecoverableSocket(url, {
3981
+ const sock = new RecoverableSocket(url, {
3866
3982
  abortSignal: ctx.signal,
3867
3983
  connectionTimeoutMs: 1e4,
3868
3984
  heartbeatIntervalMs: 2e4,
3869
3985
  pongTimeoutMs: 1e4
3870
3986
  });
3871
- this.socket.registerOnStateChangeCallback((state) => this.handleStateChange(state, ctx));
3872
- this.socket.registerOnErrorCallback((error) => {
3987
+ this.socket = sock;
3988
+ sock.registerOnStateChangeCallback((state) => this.handleStateChange(state, ctx));
3989
+ sock.registerOnErrorCallback((error) => {
3873
3990
  ctx.notifier.error(`WS: transport error: ${error.message}`);
3874
3991
  });
3875
- this.socket.registerOnConnectedCallback((_ws) => {
3992
+ sock.registerOnConnectedCallback((_ws) => {
3993
+ if (this.socket !== sock)
3994
+ return;
3876
3995
  this.retryAttempt = 0;
3877
3996
  this.clearCountdown();
3878
- ctx.notifier.success(`WS: connected (${url})`);
3997
+ ctx.notifier.success(ctx.flags.debug ? `WS: connected (${url})` : `WS: connected`);
3879
3998
  ctx.bus.emit("ws:connected", { url, attempt: 0 });
3880
3999
  });
3881
- this.socket.registerOnDisconnectedCallback((code, reasonStr) => {
4000
+ sock.registerOnDisconnectedCallback((code, reasonStr) => {
4001
+ if (this.socket !== sock) {
4002
+ ctx.notifier.info(`WS: ignoring stale disconnect (code=${code})`, { scope: "system" /* System */ });
4003
+ return;
4004
+ }
3882
4005
  ctx.notifier.warn(`WS: disconnected code=${code} reason=${reasonStr || "-"}`);
3883
4006
  ctx.bus.emit("ws:disconnected", { code, reason: reasonStr });
3884
4007
  if (code === 4001) {
3885
4008
  ctx.notifier.info("WS: auth disconnect (4001) -> waiting for auth", { scope: "system" /* System */ });
3886
4009
  return;
3887
4010
  }
4011
+ if (code === 4011) {
4012
+ this.stopped = true;
4013
+ this.socket?.stop();
4014
+ this.socket = null;
4015
+ return;
4016
+ }
3888
4017
  this.ensureStarted(ctx, "socket:disconnected");
3889
4018
  });
3890
- this.socket.registerOnMessageCallback((data) => {
4019
+ sock.registerOnMessageCallback((data) => {
4020
+ if (this.socket !== sock)
4021
+ return;
3891
4022
  const text = typeof data === "string" ? data : data.toString("utf8");
3892
4023
  ctx.bus.emit("ws:rx", { text });
3893
4024
  });
@@ -3981,8 +4112,8 @@ var WebSocketOrchestrator = class {
3981
4112
 
3982
4113
  // agent/src/cli/utils/configuration.writer.ts
3983
4114
  import fs4 from "node:fs";
3984
- import path4 from "node:path";
3985
- import os4 from "node:os";
4115
+ import path6 from "node:path";
4116
+ import os5 from "node:os";
3986
4117
  import defaultTo3 from "lodash/defaultTo.js";
3987
4118
  var ConfigurationWriter = class {
3988
4119
  /**
@@ -3991,7 +4122,7 @@ var ConfigurationWriter = class {
3991
4122
  */
3992
4123
  static save(config) {
3993
4124
  const configDir = this.getApplicationConfigFolder();
3994
- const configPath = path4.join(configDir, "config.json");
4125
+ const configPath = path6.join(configDir, "config.json");
3995
4126
  const safe = this.stripSecrets(config);
3996
4127
  fs4.mkdirSync(configDir, { recursive: true });
3997
4128
  fs4.writeFileSync(configPath, JSON.stringify(safe, null, 2), "utf8");
@@ -4018,9 +4149,9 @@ var ConfigurationWriter = class {
4018
4149
  static getApplicationConfigFolder() {
4019
4150
  const appData = defaultTo3(
4020
4151
  process.env["APPDATA"],
4021
- path4.join(os4.homedir(), "AppData", "Roaming")
4152
+ path6.join(os5.homedir(), "AppData", "Roaming")
4022
4153
  );
4023
- return path4.join(appData, "DeviceLink");
4154
+ return path6.join(appData, "DeviceLink");
4024
4155
  }
4025
4156
  };
4026
4157
 
@@ -4558,6 +4689,7 @@ var AuthOrchestrator = class {
4558
4689
  connectionId = null;
4559
4690
  sessionId = null;
4560
4691
  accessToken;
4692
+ rejectedByConflict = false;
4561
4693
  refreshGroupKey = "auth:refresh";
4562
4694
  refreshCycle = 0;
4563
4695
  refreshScheduler = null;
@@ -4591,6 +4723,11 @@ var AuthOrchestrator = class {
4591
4723
  "Auth: signin completed; requesting ws reconnect",
4592
4724
  { scope: "system" /* System */ }
4593
4725
  );
4726
+ this.rejectedByConflict = false;
4727
+ if (this.refreshRetryScheduler) {
4728
+ this.refreshRetryScheduler.stop();
4729
+ this.refreshRetryScheduler = null;
4730
+ }
4594
4731
  this.ensureAccessExpPersisted(ctx, "signin");
4595
4732
  await this.ensureRefreshScheduling(ctx, "signin");
4596
4733
  ctx.bus.emit("auth:ws:reconnect", void 0);
@@ -4647,6 +4784,14 @@ var AuthOrchestrator = class {
4647
4784
  { scope: "system" /* System */ }
4648
4785
  );
4649
4786
  this.resetConnectionState();
4787
+ if (p.code === 4011) {
4788
+ ctx.notifier.warn(
4789
+ 'Connection rejected - another device is active on this workspace. Run "signin" to try again.'
4790
+ );
4791
+ this.rejectedByConflict = true;
4792
+ ctx.bus.emit("ws:disconnect", { reason: "REPLACEMENT_REJECTED" });
4793
+ return;
4794
+ }
4650
4795
  if (p.code === 4001) {
4651
4796
  const refreshRes = await ctx.engine.secrets.get("refreshToken");
4652
4797
  if (!refreshRes.ok || !refreshRes.value) {
@@ -4669,7 +4814,8 @@ var AuthOrchestrator = class {
4669
4814
  // --- boot path unchanged except it must schedule refresh always ---
4670
4815
  async onEventConfigLoaded(config, ctx) {
4671
4816
  ctx.notifier.info(
4672
- "Initializing with cached configuration"
4817
+ "Initializing with cached configuration",
4818
+ { scope: "system" /* System */ }
4673
4819
  );
4674
4820
  if (!config.device?.id) {
4675
4821
  ctx.notifier.error("Device ID is not available");
@@ -4698,7 +4844,6 @@ var AuthOrchestrator = class {
4698
4844
  ctx.notifier.warn("Auth: not signed in; run signin");
4699
4845
  return;
4700
4846
  }
4701
- ctx.bus.emit("auth:ws:reconnect", void 0);
4702
4847
  }
4703
4848
  isAccessLikelyUsable(ctx) {
4704
4849
  const jwt = this.accessToken;
@@ -4715,7 +4860,8 @@ var AuthOrchestrator = class {
4715
4860
  async waitForApproval(api, startResponse, ctx) {
4716
4861
  const deadline = Date.now() + startResponse.expires_in * 1e3;
4717
4862
  ctx.notifier.info(
4718
- "Waiting for approval... (streaming)"
4863
+ "Waiting for approval... (streaming)",
4864
+ { scope: "system" /* System */ }
4719
4865
  );
4720
4866
  try {
4721
4867
  return await api.deviceAuthStream({
@@ -4812,7 +4958,8 @@ var AuthOrchestrator = class {
4812
4958
  }
4813
4959
  if (streamResp.needs_proof) {
4814
4960
  ctx.notifier.info(
4815
- "Sending proof-of-possession signature..."
4961
+ "Sending proof-of-possession signature...",
4962
+ { scope: "system" /* System */ }
4816
4963
  );
4817
4964
  const privKeyObj = crypto8.createPrivateKey(privateKeyPem);
4818
4965
  const challengeBytes = Buffer.from(
@@ -4827,9 +4974,9 @@ var AuthOrchestrator = class {
4827
4974
  challenge: startResponse.challenge,
4828
4975
  signature_b64: signatureB64
4829
4976
  });
4830
- ctx.notifier.info("Proof-of-possession verified");
4977
+ ctx.notifier.info("Proof-of-possession verified", { scope: "system" /* System */ });
4831
4978
  }
4832
- ctx.notifier.info("Fetching tokens...");
4979
+ ctx.notifier.info("Fetching tokens...", { scope: "system" /* System */ });
4833
4980
  const tokenResp = await api.deviceAuthToken({
4834
4981
  device_code: startResponse.device_code
4835
4982
  });
@@ -4963,6 +5110,9 @@ var AuthOrchestrator = class {
4963
5110
  ctx.bus.emit("auth:session_conflict", msg);
4964
5111
  void this.handleSessionConflict(ctx, msg);
4965
5112
  return;
5113
+ case "quiesce":
5114
+ void this.handleQuiesce(ctx, msg);
5115
+ return;
4966
5116
  default:
4967
5117
  return;
4968
5118
  }
@@ -4973,14 +5123,41 @@ var AuthOrchestrator = class {
4973
5123
  this.conflictTimer = null;
4974
5124
  }
4975
5125
  this.pendingConflictRequest = msg;
5126
+ ctx.notifier.warn(
5127
+ `Another device is already connected to this workspace. Type "replace" to disconnect it, or this connection will be rejected in ${Math.round(msg.deadline_ms / 1e3)}s.`
5128
+ );
4976
5129
  const deadlineMs = Math.min(msg.deadline_ms, 15e3);
4977
5130
  this.conflictTimer = setTimeout(() => {
4978
5131
  this.conflictTimer = null;
4979
5132
  if (this.pendingConflictRequest?.new_session_id === msg.new_session_id) {
5133
+ ctx.notifier.info("No response - keeping existing device connected.");
4980
5134
  this.sendSessionConflictResolve(ctx, "KEEP_OLD");
4981
5135
  }
4982
5136
  }, deadlineMs);
4983
5137
  }
5138
+ async handleQuiesce(ctx, msg) {
5139
+ ctx.notifier.warn(
5140
+ `This device is being replaced by another connection. Finishing in-flight operations...`
5141
+ );
5142
+ const hasActiveJobs = ctx.engine.hasRunningJobs?.() ?? false;
5143
+ if (hasActiveJobs) {
5144
+ const deadline = Math.min(msg.deadline_ms - 2e3, 12e3);
5145
+ const start = Date.now();
5146
+ while (Date.now() - start < deadline) {
5147
+ if (!(ctx.engine.hasRunningJobs?.() ?? false))
5148
+ break;
5149
+ await new Promise((r) => setTimeout(r, 500));
5150
+ }
5151
+ }
5152
+ const status = ctx.engine.hasRunningJobs?.() ?? false ? "BUSY" : "IDLE";
5153
+ this.send(ctx, {
5154
+ type: "quiesce_ack",
5155
+ session_id: msg.session_id,
5156
+ status
5157
+ });
5158
+ ctx.notifier.info("Device replaced by new connection. Disconnecting.");
5159
+ this.socket?.stop();
5160
+ }
4984
5161
  sendSessionConflictResolve(ctx, choice) {
4985
5162
  const req = this.pendingConflictRequest;
4986
5163
  if (!req)
@@ -5242,6 +5419,10 @@ var AuthOrchestrator = class {
5242
5419
  if (this.accessToken && !ctx.engine.configurationContext.configuration.tokens?.accessTokenExpSec) {
5243
5420
  this.ensureAccessExpPersisted(ctx, reason);
5244
5421
  }
5422
+ if (this.refreshRetryScheduler) {
5423
+ this.refreshRetryScheduler.stop();
5424
+ this.refreshRetryScheduler = null;
5425
+ }
5245
5426
  this.scheduleSilentRefresh(ctx, reason);
5246
5427
  }
5247
5428
  async scheduleSilentRefresh(ctx, reason) {
@@ -5440,7 +5621,7 @@ var AuthOrchestrator = class {
5440
5621
 
5441
5622
  // agent/src/cli/orchestrators/tools/executers/fs-list.ts
5442
5623
  import fs7 from "node:fs/promises";
5443
- import path7 from "node:path";
5624
+ import path9 from "node:path";
5444
5625
 
5445
5626
  // agent/src/cli/orchestrators/tools/executers/base-executer.ts
5446
5627
  var BaseExecuter = class {
@@ -5476,7 +5657,7 @@ var BaseExecuter = class {
5476
5657
  };
5477
5658
 
5478
5659
  // agent/src/cli/orchestrators/tools/util/ignore.ts
5479
- import path5 from "node:path";
5660
+ import path7 from "node:path";
5480
5661
  import fs5 from "node:fs";
5481
5662
  function matchesDefaultDenylist(relPath) {
5482
5663
  const normalized = relPath.replace(/\\/g, "/");
@@ -5527,7 +5708,7 @@ function loadIgnoreRules(cfg, directory_id, workspace_id) {
5527
5708
  return DEFAULT_IGNORE_PATTERNS;
5528
5709
  }
5529
5710
  const rules = [];
5530
- const ignoreFilePath = path5.join(directory.path, ".devicelinkignore");
5711
+ const ignoreFilePath = path7.join(directory.path, ".devicelinkignore");
5531
5712
  if (fs5.existsSync(ignoreFilePath)) {
5532
5713
  try {
5533
5714
  const content = fs5.readFileSync(ignoreFilePath, "utf-8");
@@ -5575,7 +5756,7 @@ function shouldIgnore(relPath, isDir, ctx, cfg) {
5575
5756
  }
5576
5757
  return { shouldIgnore: false };
5577
5758
  }
5578
- function matchesGlob(path11, pattern, isDir) {
5759
+ function matchesGlob(path13, pattern, isDir) {
5579
5760
  let regexStr = "^";
5580
5761
  let i = 0;
5581
5762
  while (i < pattern.length) {
@@ -5602,15 +5783,15 @@ function matchesGlob(path11, pattern, isDir) {
5602
5783
  regexStr += "$";
5603
5784
  try {
5604
5785
  const regex = new RegExp(regexStr, "i");
5605
- return regex.test(path11);
5786
+ return regex.test(path13);
5606
5787
  } catch {
5607
- return path11.toLowerCase().includes(pattern.toLowerCase());
5788
+ return path13.toLowerCase().includes(pattern.toLowerCase());
5608
5789
  }
5609
5790
  }
5610
5791
 
5611
5792
  // agent/src/cli/orchestrators/tools/util/_util.ts
5612
5793
  import fs6 from "node:fs/promises";
5613
- import path6 from "node:path";
5794
+ import path8 from "node:path";
5614
5795
  function normalizeRelPath(input) {
5615
5796
  const raw = (input ?? "").trim();
5616
5797
  if (!raw)
@@ -5629,7 +5810,7 @@ function normalizeRelPath(input) {
5629
5810
  });
5630
5811
  }
5631
5812
  const posixish = raw.replace(/\\/g, "/");
5632
- const norm = path6.posix.normalize(posixish);
5813
+ const norm = path8.posix.normalize(posixish);
5633
5814
  if (norm === ".." || norm.startsWith("../")) {
5634
5815
  throw toolErr("PATH_ESCAPE", "Path escapes the root.", {
5635
5816
  hint: 'Paths cannot use ".." to escape the configured root directory. Use paths relative to the root, like "src/file.ts" or "subdir/file.ts".'
@@ -5644,19 +5825,19 @@ function normalizeRelPath(input) {
5644
5825
  }
5645
5826
  async function resolveInRoot(rootPath, inputRel) {
5646
5827
  const rel = normalizeRelPath(inputRel);
5647
- const absRoot = path6.resolve(rootPath);
5828
+ const absRoot = path8.resolve(rootPath);
5648
5829
  const realRoot = await fs6.realpath(absRoot);
5649
- const candidate = path6.resolve(realRoot, rel === "." ? "" : rel);
5650
- const relToRoot = path6.relative(realRoot, candidate);
5651
- if (relToRoot.startsWith("..") || path6.isAbsolute(relToRoot)) {
5830
+ const candidate = path8.resolve(realRoot, rel === "." ? "" : rel);
5831
+ const relToRoot = path8.relative(realRoot, candidate);
5832
+ if (relToRoot.startsWith("..") || path8.isAbsolute(relToRoot)) {
5652
5833
  throw toolErr("PATH_ESCAPE", "Path escapes the root.", {
5653
5834
  hint: "The resolved path escapes the configured root directory. Ensure the path stays within the root boundaries."
5654
5835
  });
5655
5836
  }
5656
5837
  try {
5657
5838
  const realCandidate = await fs6.realpath(candidate);
5658
- const rel2 = path6.relative(realRoot, realCandidate);
5659
- if (rel2.startsWith("..") || path6.isAbsolute(rel2)) {
5839
+ const rel2 = path8.relative(realRoot, realCandidate);
5840
+ if (rel2.startsWith("..") || path8.isAbsolute(rel2)) {
5660
5841
  throw toolErr("PATH_ESCAPE", "Symlink/junction escape detected.", {
5661
5842
  hint: "A symlink or junction in the path resolves outside the configured root directory. Ensure all symlinks stay within the root boundaries."
5662
5843
  });
@@ -5669,16 +5850,16 @@ async function resolveInRoot(rootPath, inputRel) {
5669
5850
  for (let i = parts.length; i > 0; i--) {
5670
5851
  const parentRel = parts.slice(0, i).join("/");
5671
5852
  const restRel = parts.slice(i).join("/");
5672
- const parentCandidate = path6.resolve(realRoot, parentRel);
5853
+ const parentCandidate = path8.resolve(realRoot, parentRel);
5673
5854
  try {
5674
5855
  const realParent = await fs6.realpath(parentCandidate);
5675
- const relParent = path6.relative(realRoot, realParent);
5676
- if (relParent.startsWith("..") || path6.isAbsolute(relParent)) {
5856
+ const relParent = path8.relative(realRoot, realParent);
5857
+ if (relParent.startsWith("..") || path8.isAbsolute(relParent)) {
5677
5858
  throw toolErr("PATH_ESCAPE", "Symlink/junction escape detected.", {
5678
5859
  hint: "A symlink or junction in the path resolves outside the configured root directory. Ensure all symlinks stay within the root boundaries."
5679
5860
  });
5680
5861
  }
5681
- return { absTarget: restRel ? path6.join(realParent, restRel) : realParent, rel };
5862
+ return { absTarget: restRel ? path8.join(realParent, restRel) : realParent, rel };
5682
5863
  } catch (err) {
5683
5864
  if (err?.code === "ENOENT")
5684
5865
  continue;
@@ -5860,8 +6041,8 @@ var FsListExecuter = class extends BaseExecuter {
5860
6041
  directory_path: directory.path
5861
6042
  };
5862
6043
  for (const d of visibleDirents) {
5863
- const full = path7.join(absTarget, d.name);
5864
- const rawRelPath = rel === "." ? d.name : path7.posix.join(rel, d.name);
6044
+ const full = path9.join(absTarget, d.name);
6045
+ const rawRelPath = rel === "." ? d.name : path9.posix.join(rel, d.name);
5865
6046
  const entryRelPath = rawRelPath.replace(/\\/g, "/");
5866
6047
  let type = "other";
5867
6048
  if (d.isDirectory())
@@ -5899,7 +6080,7 @@ var FsListExecuter = class extends BaseExecuter {
5899
6080
  } catch {
5900
6081
  }
5901
6082
  }
5902
- const ext = type === "file" ? path7.extname(d.name).slice(1) : void 0;
6083
+ const ext = type === "file" ? path9.extname(d.name).slice(1) : void 0;
5903
6084
  if (ext && (detail === "stat" || detail === "digest")) {
5904
6085
  extCounts.set(ext, (extCounts.get(ext) ?? 0) + 1);
5905
6086
  }
@@ -6111,11 +6292,11 @@ var FsInfoExecuter = class extends BaseExecuter {
6111
6292
 
6112
6293
  // agent/src/cli/orchestrators/tools/executers/fs-tree.ts
6113
6294
  import fs11 from "node:fs/promises";
6114
- import path9 from "node:path";
6295
+ import path11 from "node:path";
6115
6296
 
6116
6297
  // agent/src/cli/orchestrators/tools/executers/fs-grep.ts
6117
6298
  import fs10 from "node:fs/promises";
6118
- import path8 from "node:path";
6299
+ import path10 from "node:path";
6119
6300
  function matchesFileGlob(relPath, fileGlob) {
6120
6301
  let regexStr = "^";
6121
6302
  let i = 0;
@@ -6211,8 +6392,8 @@ async function grepDirectory(absPath, relPath, rootPath, query, mode, fileGlob,
6211
6392
  for (const entry of entries) {
6212
6393
  if (filesScanned.value >= maxFiles || matches.length >= maxMatches)
6213
6394
  break;
6214
- const entryAbsPath = path8.join(absPath, entry.name);
6215
- const entryRelPath = relPath === "." ? entry.name : path8.posix.join(relPath, entry.name);
6395
+ const entryAbsPath = path10.join(absPath, entry.name);
6396
+ const entryRelPath = relPath === "." ? entry.name : path10.posix.join(relPath, entry.name);
6216
6397
  const ignoreResult = shouldIgnore(entryRelPath, entry.isDirectory(), ignoreCtx, cfg);
6217
6398
  if (ignoreResult.shouldIgnore) {
6218
6399
  ignoredCount.value++;
@@ -6505,10 +6686,10 @@ async function buildTree(absPath, relPath, depth, maxDepth, only, includeHidden,
6505
6686
  const errorMessage = toolError2?.message ?? e?.message ?? String(e) ?? "Unknown error";
6506
6687
  summary.total_entries++;
6507
6688
  const node2 = {
6508
- name: path9.basename(relPath) || relPath,
6689
+ name: path11.basename(relPath) || relPath,
6509
6690
  type: "other",
6510
6691
  path: entryRel,
6511
- hidden: path9.basename(relPath)?.startsWith("."),
6692
+ hidden: path11.basename(relPath)?.startsWith("."),
6512
6693
  ignored: false
6513
6694
  };
6514
6695
  if (errorMap) {
@@ -6542,10 +6723,10 @@ async function buildTree(absPath, relPath, depth, maxDepth, only, includeHidden,
6542
6723
  summary.dirs++;
6543
6724
  else if (type === "symlink")
6544
6725
  summary.symlinks++;
6545
- const isHidden = path9.basename(relPath)?.startsWith(".");
6726
+ const isHidden = path11.basename(relPath)?.startsWith(".");
6546
6727
  if (isHidden)
6547
6728
  summary.hidden++;
6548
- const ext = type === "file" ? path9.extname(path9.basename(relPath) || relPath).slice(1) : void 0;
6729
+ const ext = type === "file" ? path11.extname(path11.basename(relPath) || relPath).slice(1) : void 0;
6549
6730
  if (ext && (detail === "stat" || detail === "digest")) {
6550
6731
  extCounts.set(ext, (extCounts.get(ext) ?? 0) + 1);
6551
6732
  }
@@ -6553,7 +6734,7 @@ async function buildTree(absPath, relPath, depth, maxDepth, only, includeHidden,
6553
6734
  summary.total_file_bytes = (summary.total_file_bytes ?? 0) + (stat.size ?? 0);
6554
6735
  }
6555
6736
  const node = {
6556
- name: path9.basename(relPath) || relPath,
6737
+ name: path11.basename(relPath) || relPath,
6557
6738
  type,
6558
6739
  path: entryRel,
6559
6740
  size: (detail === "stat" || detail === "digest") && stat.isFile() ? stat.size : void 0,
@@ -6600,8 +6781,8 @@ async function buildTree(absPath, relPath, depth, maxDepth, only, includeHidden,
6600
6781
  for (const entry of filtered) {
6601
6782
  if (currentCount.value >= maxEntries)
6602
6783
  break;
6603
- const childAbs = path9.join(absPath, entry.name);
6604
- const childRelPath = relPath === "." ? entry.name : path9.posix.join(relPath, entry.name);
6784
+ const childAbs = path11.join(absPath, entry.name);
6785
+ const childRelPath = relPath === "." ? entry.name : path11.posix.join(relPath, entry.name);
6605
6786
  const childRel = childRelPath.replace(/\\/g, "/");
6606
6787
  const preIgnore = shouldIgnore(childRel, entry.isDirectory(), ignoreCtx, cfg);
6607
6788
  if (preIgnore.shouldIgnore) {
@@ -6642,10 +6823,10 @@ async function buildTree(absPath, relPath, depth, maxDepth, only, includeHidden,
6642
6823
  }
6643
6824
  summary.total_entries++;
6644
6825
  const errorNode = {
6645
- name: path9.basename(childRelPath) || childRelPath,
6826
+ name: path11.basename(childRelPath) || childRelPath,
6646
6827
  type: "other",
6647
6828
  path: childRel,
6648
- hidden: path9.basename(childRelPath)?.startsWith("."),
6829
+ hidden: path11.basename(childRelPath)?.startsWith("."),
6649
6830
  ignored: false
6650
6831
  };
6651
6832
  if (only === "files" || only === "dirs") {
@@ -6832,7 +7013,7 @@ var FsTreeExecuter = class extends BaseExecuter {
6832
7013
 
6833
7014
  // agent/src/cli/orchestrators/tools/executers/fs-search.ts
6834
7015
  import fs12 from "node:fs/promises";
6835
- import path10 from "node:path";
7016
+ import path12 from "node:path";
6836
7017
  function matchesGlob2(name, pattern) {
6837
7018
  let regexStr = "^";
6838
7019
  let i = 0;
@@ -6954,8 +7135,8 @@ async function searchDirectory(absPath, relPath, rootPath, query, mode, recursiv
6954
7135
  for (const entry of visibleEntries) {
6955
7136
  if (results.length >= maxResults)
6956
7137
  break;
6957
- const entryAbsPath = path10.join(absPath, entry.name);
6958
- const entryRelPath = relPath === "." ? entry.name : path10.posix.join(relPath, entry.name);
7138
+ const entryAbsPath = path12.join(absPath, entry.name);
7139
+ const entryRelPath = relPath === "." ? entry.name : path12.posix.join(relPath, entry.name);
6959
7140
  const ignoreResult = shouldIgnore(entryRelPath, entry.isDirectory(), ignoreCtx, cfg);
6960
7141
  if (ignoreResult.shouldIgnore) {
6961
7142
  ignoredCount.value++;
@@ -7005,7 +7186,7 @@ async function searchDirectory(absPath, relPath, rootPath, query, mode, recursiv
7005
7186
  } catch {
7006
7187
  }
7007
7188
  }
7008
- const ext = type === "file" ? path10.extname(entry.name).slice(1) : void 0;
7189
+ const ext = type === "file" ? path12.extname(entry.name).slice(1) : void 0;
7009
7190
  const score = calculateScore({ name: entry.name, path: entryRelPath }, preferPaths, avoidPaths);
7010
7191
  results.push({
7011
7192
  name: entry.name,
@@ -7073,8 +7254,8 @@ var FsSearchExecuter = class extends BaseExecuter {
7073
7254
  const st = await fs12.stat(absTarget);
7074
7255
  if (!st.isDirectory()) {
7075
7256
  if (st.isFile()) {
7076
- searchPath = path10.dirname(absTarget);
7077
- const parentRelPath = path10.posix.dirname(rel);
7257
+ searchPath = path12.dirname(absTarget);
7258
+ const parentRelPath = path12.posix.dirname(rel);
7078
7259
  searchRelPath = parentRelPath === "." ? "." : parentRelPath;
7079
7260
  messages.push({
7080
7261
  type: "warning",
@@ -7369,7 +7550,7 @@ var FsListResult = class extends BaseToolResult {
7369
7550
  const itemCount = items?.length ?? 0;
7370
7551
  const totalCount = meta?.count ?? 0;
7371
7552
  const ignoredCount = filtered?.ignored_count ?? 0;
7372
- const path11 = context?.path ?? "unknown";
7553
+ const path13 = context?.path ?? "unknown";
7373
7554
  const parts = [];
7374
7555
  if (itemCount > 0) {
7375
7556
  parts.push(`${itemCount} item${itemCount !== 1 ? "s" : ""}`);
@@ -7381,7 +7562,7 @@ var FsListResult = class extends BaseToolResult {
7381
7562
  parts.push("has_more");
7382
7563
  }
7383
7564
  const description = parts.length > 0 ? parts.join(", ") : "empty";
7384
- return `path=${path11}, ${description}`;
7565
+ return `path=${path13}, ${description}`;
7385
7566
  }
7386
7567
  };
7387
7568
 
@@ -7404,10 +7585,10 @@ var FsReadResult = class extends BaseToolResult {
7404
7585
  return "no result";
7405
7586
  }
7406
7587
  const { bytes_read, eof, context } = this.output;
7407
- const path11 = context?.path ?? "unknown";
7588
+ const path13 = context?.path ?? "unknown";
7408
7589
  const size = bytes_read ?? 0;
7409
7590
  const truncated = !eof;
7410
- const parts = [`path=${path11}`, `${size} bytes`];
7591
+ const parts = [`path=${path13}`, `${size} bytes`];
7411
7592
  if (truncated) {
7412
7593
  parts.push("truncated");
7413
7594
  }
@@ -7434,9 +7615,9 @@ var FsInfoResult = class extends BaseToolResult {
7434
7615
  return "no result";
7435
7616
  }
7436
7617
  const { type, size, context } = this.output;
7437
- const path11 = context?.path ?? "unknown";
7618
+ const path13 = context?.path ?? "unknown";
7438
7619
  const sizeStr = size !== void 0 ? `${size} bytes` : "no size";
7439
- return `path=${path11}, type=${type}, ${sizeStr}`;
7620
+ return `path=${path13}, type=${type}, ${sizeStr}`;
7440
7621
  }
7441
7622
  };
7442
7623
 
@@ -7459,9 +7640,9 @@ var FsTreeResult = class extends BaseToolResult {
7459
7640
  return "no result";
7460
7641
  }
7461
7642
  const { tree, filtered, summary, context } = this.output;
7462
- const path11 = context?.path ?? "unknown";
7643
+ const path13 = context?.path ?? "unknown";
7463
7644
  const ignoredCount = filtered?.ignored_count ?? 0;
7464
- const parts = [`path=${path11}`];
7645
+ const parts = [`path=${path13}`];
7465
7646
  if (summary) {
7466
7647
  const { total_entries, files, dirs } = summary;
7467
7648
  if (total_entries !== void 0) {
@@ -7515,10 +7696,10 @@ var FsSearchResult = class extends BaseToolResult {
7515
7696
  return "no result";
7516
7697
  }
7517
7698
  const { results, meta, filtered, context } = this.output;
7518
- const path11 = context?.path ?? "unknown";
7699
+ const path13 = context?.path ?? "unknown";
7519
7700
  const resultCount = results?.length ?? 0;
7520
7701
  const ignoredCount = filtered?.ignored_count ?? 0;
7521
- const parts = [`path=${path11}`, `${resultCount} result${resultCount !== 1 ? "s" : ""}`];
7702
+ const parts = [`path=${path13}`, `${resultCount} result${resultCount !== 1 ? "s" : ""}`];
7522
7703
  if (ignoredCount > 0) {
7523
7704
  parts.push(`${ignoredCount} ignored`);
7524
7705
  }
@@ -7548,12 +7729,12 @@ var FsGrepResult = class extends BaseToolResult {
7548
7729
  return "no result";
7549
7730
  }
7550
7731
  const { matches, meta, filtered, context } = this.output;
7551
- const path11 = context?.path ?? "unknown";
7732
+ const path13 = context?.path ?? "unknown";
7552
7733
  const matchCount = matches?.length ?? 0;
7553
7734
  const filesScanned = meta?.files_scanned ?? 0;
7554
7735
  const ignoredCount = filtered?.ignored_count ?? 0;
7555
7736
  const parts = [
7556
- `path=${path11}`,
7737
+ `path=${path13}`,
7557
7738
  `${matchCount} match${matchCount !== 1 ? "es" : ""}`,
7558
7739
  `${filesScanned} file${filesScanned !== 1 ? "s" : ""} scanned`
7559
7740
  ];
@@ -7677,6 +7858,7 @@ var ToolsOrchestrator = class {
7677
7858
  const availableDirectoryIds = config.directories.map((r) => r.id).join(", ") || "none";
7678
7859
  const availableDirectoryPaths = config.directories.map((r) => r.path).join(", ") || "none";
7679
7860
  ctx.notifier.info(`Tool call: ${toolName} (job=${jobId}, directory_id=${directoryId || "MISSING"}, available_ids=[${availableDirectoryIds}], available_paths=[${availableDirectoryPaths}], directory_count=${config.directories.length})`, { scope: "system" /* System */ });
7861
+ ctx.notifier.info(`Executing: ${toolName}`);
7680
7862
  const toolStartTime = Date.now();
7681
7863
  try {
7682
7864
  const toolRegistry = new ToolExecuterRegistry(config);
@@ -7687,9 +7869,9 @@ var ToolsOrchestrator = class {
7687
7869
  const timing = calculateTiming(toolStartTime);
7688
7870
  const result = createToolResult(toolName, jobId, raw, timing.execution_sec);
7689
7871
  if (result.ok) {
7690
- ctx.notifier.success(result.toString(), { ts: Date.now(), kind: "success" /* Success */, scope: "system" /* System */ });
7872
+ ctx.notifier.success(result.toString());
7691
7873
  } else {
7692
- ctx.notifier.error(result.toString(), { scope: "system" /* System */ });
7874
+ ctx.notifier.error(result.toString());
7693
7875
  }
7694
7876
  } catch (e) {
7695
7877
  const toolResult = formatToolErrorResponse(jobId, toolName, e, toolStartTime);
@@ -7701,7 +7883,7 @@ var ToolsOrchestrator = class {
7701
7883
  error: e instanceof Error ? { code: "INTERNAL", message: e.message } : { code: "INTERNAL", message: String(e) }
7702
7884
  };
7703
7885
  const result = createToolResult(toolName, jobId, errorResult, timing.execution_sec);
7704
- ctx.notifier.error(result.toString(), { scope: "system" /* System */ });
7886
+ ctx.notifier.error(result.toString());
7705
7887
  }
7706
7888
  }
7707
7889
  };
@@ -7917,7 +8099,7 @@ var DirectoriesJob = class extends JobType {
7917
8099
  return {
7918
8100
  id: r.id,
7919
8101
  name: r.name,
7920
- path: r.path,
8102
+ path: toTildeHomePath(r.path) ?? r.name,
7921
8103
  path_hint: r.tag || void 0
7922
8104
  };
7923
8105
  });