open-agents-ai 0.67.1 → 0.68.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.js +1433 -272
  2. package/package.json +7 -2
package/dist/index.js CHANGED
@@ -14626,10 +14626,10 @@ ${marker}` : marker);
14626
14626
  if (!this._workingDirectory)
14627
14627
  return;
14628
14628
  try {
14629
- const { mkdirSync: mkdirSync16, writeFileSync: writeFileSync14 } = __require("node:fs");
14630
- const { join: join45 } = __require("node:path");
14631
- const sessionDir = join45(this._workingDirectory, ".oa", "session", this._sessionId);
14632
- mkdirSync16(sessionDir, { recursive: true });
14629
+ const { mkdirSync: mkdirSync17, writeFileSync: writeFileSync15 } = __require("node:fs");
14630
+ const { join: join46 } = __require("node:path");
14631
+ const sessionDir = join46(this._workingDirectory, ".oa", "session", this._sessionId);
14632
+ mkdirSync17(sessionDir, { recursive: true });
14633
14633
  const checkpoint = {
14634
14634
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
14635
14635
  sessionId: this._sessionId,
@@ -14641,7 +14641,7 @@ ${marker}` : marker);
14641
14641
  memexEntryCount: this._memexArchive.size,
14642
14642
  fileRegistrySize: this._fileRegistry.size
14643
14643
  };
14644
- writeFileSync14(join45(sessionDir, "checkpoint.json"), JSON.stringify(checkpoint, null, 2));
14644
+ writeFileSync15(join46(sessionDir, "checkpoint.json"), JSON.stringify(checkpoint, null, 2));
14645
14645
  } catch {
14646
14646
  }
14647
14647
  }
@@ -19129,12 +19129,12 @@ var require_extension = __commonJS({
19129
19129
  var require_websocket = __commonJS({
19130
19130
  "node_modules/.pnpm/ws@8.19.0/node_modules/ws/lib/websocket.js"(exports, module) {
19131
19131
  "use strict";
19132
- var EventEmitter5 = __require("events");
19132
+ var EventEmitter7 = __require("events");
19133
19133
  var https = __require("https");
19134
19134
  var http = __require("http");
19135
19135
  var net = __require("net");
19136
19136
  var tls = __require("tls");
19137
- var { randomBytes: randomBytes6, createHash: createHash2 } = __require("crypto");
19137
+ var { randomBytes: randomBytes8, createHash: createHash4 } = __require("crypto");
19138
19138
  var { Duplex, Readable } = __require("stream");
19139
19139
  var { URL: URL3 } = __require("url");
19140
19140
  var PerMessageDeflate = require_permessage_deflate();
@@ -19161,7 +19161,7 @@ var require_websocket = __commonJS({
19161
19161
  var protocolVersions = [8, 13];
19162
19162
  var readyStates = ["CONNECTING", "OPEN", "CLOSING", "CLOSED"];
19163
19163
  var subprotocolRegex = /^[!#$%&'*+\-.0-9A-Z^_`|a-z~]+$/;
19164
- var WebSocket2 = class _WebSocket extends EventEmitter5 {
19164
+ var WebSocket2 = class _WebSocket extends EventEmitter7 {
19165
19165
  /**
19166
19166
  * Create a new `WebSocket`.
19167
19167
  *
@@ -19664,7 +19664,7 @@ var require_websocket = __commonJS({
19664
19664
  }
19665
19665
  }
19666
19666
  const defaultPort = isSecure ? 443 : 80;
19667
- const key = randomBytes6(16).toString("base64");
19667
+ const key = randomBytes8(16).toString("base64");
19668
19668
  const request = isSecure ? https.request : http.request;
19669
19669
  const protocolSet = /* @__PURE__ */ new Set();
19670
19670
  let perMessageDeflate;
@@ -19794,7 +19794,7 @@ var require_websocket = __commonJS({
19794
19794
  abortHandshake(websocket, socket, "Invalid Upgrade header");
19795
19795
  return;
19796
19796
  }
19797
- const digest = createHash2("sha1").update(key + GUID).digest("base64");
19797
+ const digest = createHash4("sha1").update(key + GUID).digest("base64");
19798
19798
  if (res.headers["sec-websocket-accept"] !== digest) {
19799
19799
  abortHandshake(websocket, socket, "Invalid Sec-WebSocket-Accept header");
19800
19800
  return;
@@ -20158,10 +20158,10 @@ var require_subprotocol = __commonJS({
20158
20158
  var require_websocket_server = __commonJS({
20159
20159
  "node_modules/.pnpm/ws@8.19.0/node_modules/ws/lib/websocket-server.js"(exports, module) {
20160
20160
  "use strict";
20161
- var EventEmitter5 = __require("events");
20161
+ var EventEmitter7 = __require("events");
20162
20162
  var http = __require("http");
20163
20163
  var { Duplex } = __require("stream");
20164
- var { createHash: createHash2 } = __require("crypto");
20164
+ var { createHash: createHash4 } = __require("crypto");
20165
20165
  var extension = require_extension();
20166
20166
  var PerMessageDeflate = require_permessage_deflate();
20167
20167
  var subprotocol = require_subprotocol();
@@ -20171,7 +20171,7 @@ var require_websocket_server = __commonJS({
20171
20171
  var RUNNING = 0;
20172
20172
  var CLOSING = 1;
20173
20173
  var CLOSED = 2;
20174
- var WebSocketServer2 = class extends EventEmitter5 {
20174
+ var WebSocketServer2 = class extends EventEmitter7 {
20175
20175
  /**
20176
20176
  * Create a `WebSocketServer` instance.
20177
20177
  *
@@ -20462,7 +20462,7 @@ var require_websocket_server = __commonJS({
20462
20462
  );
20463
20463
  }
20464
20464
  if (this._state > RUNNING) return abortHandshake(socket, 503);
20465
- const digest = createHash2("sha1").update(key + GUID).digest("base64");
20465
+ const digest = createHash4("sha1").update(key + GUID).digest("base64");
20466
20466
  const headers = [
20467
20467
  "HTTP/1.1 101 Switching Protocols",
20468
20468
  "Upgrade: websocket",
@@ -22440,8 +22440,958 @@ var init_expose = __esm({
22440
22440
  }
22441
22441
  });
22442
22442
 
22443
- // packages/cli/dist/tui/call-agent.js
22443
+ // packages/cli/dist/tui/p2p/types.js
22444
+ function trustLevel(tier) {
22445
+ return TRUST_ORDER.indexOf(tier);
22446
+ }
22447
+ var TRUST_ORDER;
22448
+ var init_types = __esm({
22449
+ "packages/cli/dist/tui/p2p/types.js"() {
22450
+ "use strict";
22451
+ TRUST_ORDER = ["public", "verified", "tee", "local"];
22452
+ }
22453
+ });
22454
+
22455
+ // packages/cli/dist/tui/p2p/secret-vault.js
22456
+ import { createCipheriv, createDecipheriv, randomBytes as randomBytes6, scryptSync, createHash } from "node:crypto";
22457
+ import { readFileSync as readFileSync15, writeFileSync as writeFileSync7, existsSync as existsSync21, mkdirSync as mkdirSync7 } from "node:fs";
22458
+ import { join as join29, dirname as dirname11 } from "node:path";
22459
+ var PLACEHOLDER_PREFIX, PLACEHOLDER_SUFFIX, CIPHER_ALGO, SALT_LEN, IV_LEN, KEY_LEN, SecretVault;
22460
+ var init_secret_vault = __esm({
22461
+ "packages/cli/dist/tui/p2p/secret-vault.js"() {
22462
+ "use strict";
22463
+ init_types();
22464
+ PLACEHOLDER_PREFIX = "{{OA_VAR_";
22465
+ PLACEHOLDER_SUFFIX = "}}";
22466
+ CIPHER_ALGO = "aes-256-gcm";
22467
+ SALT_LEN = 16;
22468
+ IV_LEN = 12;
22469
+ KEY_LEN = 32;
22470
+ SecretVault = class {
22471
+ storePath;
22472
+ secrets = /* @__PURE__ */ new Map();
22473
+ /** Regex cache — rebuilt when secrets change */
22474
+ _redactRegex = null;
22475
+ /** Reverse map: placeholder → secret name (for injection) */
22476
+ _placeholderToName = /* @__PURE__ */ new Map();
22477
+ constructor(storePath) {
22478
+ this.storePath = storePath;
22479
+ }
22480
+ // ── Core API ────────────────────────────────────────────────────────────
22481
+ /**
22482
+ * Register a secret. The value will be redacted from all text sent to
22483
+ * peers below the specified trust tier.
22484
+ */
22485
+ set(name, value, minTrust = "verified") {
22486
+ const normalized = name.toUpperCase().replace(/[^A-Z0-9_]/g, "_");
22487
+ const placeholder = `${PLACEHOLDER_PREFIX}${normalized}${PLACEHOLDER_SUFFIX}`;
22488
+ this.secrets.set(normalized, {
22489
+ name: normalized,
22490
+ value,
22491
+ placeholder,
22492
+ minTrust,
22493
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
22494
+ });
22495
+ this._redactRegex = null;
22496
+ this._placeholderToName.set(placeholder, normalized);
22497
+ }
22498
+ /** Remove a secret from the vault */
22499
+ delete(name) {
22500
+ const normalized = name.toUpperCase().replace(/[^A-Z0-9_]/g, "_");
22501
+ const entry = this.secrets.get(normalized);
22502
+ if (entry) {
22503
+ this._placeholderToName.delete(entry.placeholder);
22504
+ this.secrets.delete(normalized);
22505
+ this._redactRegex = null;
22506
+ return true;
22507
+ }
22508
+ return false;
22509
+ }
22510
+ /** Get a secret value by name */
22511
+ get(name) {
22512
+ const normalized = name.toUpperCase().replace(/[^A-Z0-9_]/g, "_");
22513
+ return this.secrets.get(normalized)?.value;
22514
+ }
22515
+ /** List all secret names (never exposes values) */
22516
+ list() {
22517
+ return Array.from(this.secrets.values()).map((s) => ({
22518
+ name: s.name,
22519
+ minTrust: s.minTrust,
22520
+ createdAt: s.createdAt
22521
+ }));
22522
+ }
22523
+ /** Number of registered secrets */
22524
+ get size() {
22525
+ return this.secrets.size;
22526
+ }
22527
+ // ── Redaction & Injection ───────────────────────────────────────────────
22528
+ /**
22529
+ * Redact all known secret values from text, replacing with placeholders.
22530
+ * Only redacts secrets whose minTrust is above the given peer trust level.
22531
+ *
22532
+ * @param text - The text to redact
22533
+ * @param peerTrust - The trust tier of the destination peer
22534
+ * @returns Redacted text with {{OA_VAR_*}} placeholders
22535
+ */
22536
+ redact(text, peerTrust = "public") {
22537
+ if (this.secrets.size === 0)
22538
+ return text;
22539
+ const peerLevel = trustLevel(peerTrust);
22540
+ let result = text;
22541
+ const sorted = Array.from(this.secrets.values()).filter((s) => trustLevel(s.minTrust) > peerLevel).sort((a, b) => b.value.length - a.value.length);
22542
+ for (const secret of sorted) {
22543
+ if (secret.value.length < 3)
22544
+ continue;
22545
+ const escaped = secret.value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
22546
+ result = result.replace(new RegExp(escaped, "g"), secret.placeholder);
22547
+ }
22548
+ return result;
22549
+ }
22550
+ /**
22551
+ * Inject real secret values back into text containing placeholders.
22552
+ * This is the "last-mile" operation that happens ONLY on the trusted local machine.
22553
+ *
22554
+ * @param text - Text with {{OA_VAR_*}} placeholders
22555
+ * @returns Text with real secret values restored
22556
+ */
22557
+ inject(text) {
22558
+ if (this.secrets.size === 0 || !text.includes(PLACEHOLDER_PREFIX))
22559
+ return text;
22560
+ let result = text;
22561
+ for (const [placeholder, name] of this._placeholderToName) {
22562
+ const secret = this.secrets.get(name);
22563
+ if (secret) {
22564
+ result = result.split(placeholder).join(secret.value);
22565
+ }
22566
+ }
22567
+ return result;
22568
+ }
22569
+ /**
22570
+ * Scan text for potential secrets that should be registered.
22571
+ * Returns patterns that look like API keys, tokens, passwords, etc.
22572
+ */
22573
+ scan(text) {
22574
+ const patterns = [];
22575
+ const seen = /* @__PURE__ */ new Set();
22576
+ const regexes = [
22577
+ /\b(sk-[a-zA-Z0-9]{20,})\b/g,
22578
+ // OpenAI-style
22579
+ /\b(cpk_[a-zA-Z0-9]{20,})\b/g,
22580
+ // Chutes-style
22581
+ /\b(gsk_[a-zA-Z0-9]{20,})\b/g,
22582
+ // Groq-style
22583
+ /\b(xai-[a-zA-Z0-9]{20,})\b/g,
22584
+ // xAI-style
22585
+ /\b(ghp_[a-zA-Z0-9]{36,})\b/g,
22586
+ // GitHub PAT
22587
+ /\b(glpat-[a-zA-Z0-9_-]{20,})\b/g,
22588
+ // GitLab PAT
22589
+ /\b(AKIA[0-9A-Z]{16})\b/g,
22590
+ // AWS access key
22591
+ /\b(0x[a-fA-F0-9]{40})\b/g,
22592
+ // ETH address
22593
+ /\b(0x[a-fA-F0-9]{64})\b/g,
22594
+ // ETH private key
22595
+ /\b([a-zA-Z0-9+/]{40,}={0,2})\b/g,
22596
+ // Base64 tokens (40+ chars)
22597
+ /\b(eyJ[a-zA-Z0-9_-]{10,}\.[a-zA-Z0-9_-]{10,})/g
22598
+ // JWT tokens
22599
+ ];
22600
+ for (const re of regexes) {
22601
+ let match;
22602
+ while ((match = re.exec(text)) !== null) {
22603
+ const val = match[1];
22604
+ if (!seen.has(val) && !this.isRegistered(val)) {
22605
+ seen.add(val);
22606
+ patterns.push(val);
22607
+ }
22608
+ }
22609
+ }
22610
+ return patterns;
22611
+ }
22612
+ /** Check if a value is already registered as a secret */
22613
+ isRegistered(value) {
22614
+ for (const s of this.secrets.values()) {
22615
+ if (s.value === value)
22616
+ return true;
22617
+ }
22618
+ return false;
22619
+ }
22620
+ // ── Auto-registration from environment ──────────────────────────────────
22621
+ /**
22622
+ * Auto-import secrets from environment variables matching common patterns.
22623
+ * Only imports vars that look like credentials (API keys, tokens, passwords).
22624
+ */
22625
+ importFromEnv() {
22626
+ const envPatterns = [
22627
+ /^.*API[_-]?KEY$/i,
22628
+ /^.*SECRET$/i,
22629
+ /^.*TOKEN$/i,
22630
+ /^.*PASSWORD$/i,
22631
+ /^.*PRIVATE[_-]?KEY$/i,
22632
+ /^.*AUTH$/i,
22633
+ /^.*CREDENTIAL$/i
22634
+ ];
22635
+ let count = 0;
22636
+ for (const [key, value] of Object.entries(process.env)) {
22637
+ if (!value || value.length < 8)
22638
+ continue;
22639
+ if (envPatterns.some((p) => p.test(key))) {
22640
+ if (!this.secrets.has(key)) {
22641
+ this.set(key, value, "verified");
22642
+ count++;
22643
+ }
22644
+ }
22645
+ }
22646
+ return count;
22647
+ }
22648
+ // ── Encrypted persistence ──────────────────────────────────────────────
22649
+ /**
22650
+ * Save vault to disk with AES-256-GCM encryption.
22651
+ * The passphrase is used to derive the encryption key via scrypt.
22652
+ */
22653
+ save(passphrase) {
22654
+ if (!this.storePath)
22655
+ throw new Error("No store path configured");
22656
+ const data = JSON.stringify(Array.from(this.secrets.values()).map((s) => ({
22657
+ name: s.name,
22658
+ value: s.value,
22659
+ minTrust: s.minTrust,
22660
+ createdAt: s.createdAt
22661
+ })));
22662
+ const salt = randomBytes6(SALT_LEN);
22663
+ const key = scryptSync(passphrase, salt, KEY_LEN);
22664
+ const iv = randomBytes6(IV_LEN);
22665
+ const cipher = createCipheriv(CIPHER_ALGO, key, iv);
22666
+ const encrypted = Buffer.concat([cipher.update(data, "utf8"), cipher.final()]);
22667
+ const tag = cipher.getAuthTag();
22668
+ const blob = Buffer.concat([salt, iv, tag, encrypted]);
22669
+ const dir = dirname11(this.storePath);
22670
+ if (!existsSync21(dir))
22671
+ mkdirSync7(dir, { recursive: true });
22672
+ writeFileSync7(this.storePath, blob, { mode: 384 });
22673
+ }
22674
+ /**
22675
+ * Load vault from disk, decrypting with the given passphrase.
22676
+ * Returns the number of secrets loaded.
22677
+ */
22678
+ load(passphrase) {
22679
+ if (!this.storePath || !existsSync21(this.storePath))
22680
+ return 0;
22681
+ const blob = readFileSync15(this.storePath);
22682
+ if (blob.length < SALT_LEN + IV_LEN + 16) {
22683
+ throw new Error("Vault file is corrupted (too small)");
22684
+ }
22685
+ const salt = blob.subarray(0, SALT_LEN);
22686
+ const iv = blob.subarray(SALT_LEN, SALT_LEN + IV_LEN);
22687
+ const tag = blob.subarray(SALT_LEN + IV_LEN, SALT_LEN + IV_LEN + 16);
22688
+ const encrypted = blob.subarray(SALT_LEN + IV_LEN + 16);
22689
+ const key = scryptSync(passphrase, salt, KEY_LEN);
22690
+ const decipher = createDecipheriv(CIPHER_ALGO, key, iv);
22691
+ decipher.setAuthTag(tag);
22692
+ let data;
22693
+ try {
22694
+ data = decipher.update(encrypted) + decipher.final("utf8");
22695
+ } catch {
22696
+ throw new Error("Failed to decrypt vault \u2014 wrong passphrase?");
22697
+ }
22698
+ const entries = JSON.parse(data);
22699
+ for (const entry of entries) {
22700
+ this.set(entry.name, entry.value, entry.minTrust);
22701
+ }
22702
+ return entries.length;
22703
+ }
22704
+ /** Generate a deterministic fingerprint of vault contents (for sync verification) */
22705
+ fingerprint() {
22706
+ const names = Array.from(this.secrets.keys()).sort();
22707
+ const hash = createHash("sha256");
22708
+ for (const name of names) {
22709
+ hash.update(name + ":");
22710
+ hash.update(this.secrets.get(name).value);
22711
+ hash.update("\n");
22712
+ }
22713
+ return hash.digest("base64url").slice(0, 16);
22714
+ }
22715
+ };
22716
+ }
22717
+ });
22718
+
22719
+ // packages/cli/dist/tui/p2p/peer-mesh.js
22444
22720
  import { EventEmitter as EventEmitter4 } from "node:events";
22721
+ import { createServer as createServer3 } from "node:http";
22722
+ import { randomBytes as randomBytes7, createHash as createHash2, generateKeyPairSync } from "node:crypto";
22723
+ var PING_INTERVAL_MS, PEER_TIMEOUT_MS, GOSSIP_INTERVAL_MS, MAX_PEERS, PeerMesh;
22724
+ var init_peer_mesh = __esm({
22725
+ "packages/cli/dist/tui/p2p/peer-mesh.js"() {
22726
+ "use strict";
22727
+ init_wrapper();
22728
+ PING_INTERVAL_MS = 3e4;
22729
+ PEER_TIMEOUT_MS = 9e4;
22730
+ GOSSIP_INTERVAL_MS = 6e4;
22731
+ MAX_PEERS = 50;
22732
+ PeerMesh = class extends EventEmitter4 {
22733
+ options;
22734
+ /** This node's unique peer ID (hash of public key) */
22735
+ peerId;
22736
+ /** Ed25519 keypair for identity */
22737
+ privateKey;
22738
+ publicKey;
22739
+ /** Known peers (peerId → PeerInfo) */
22740
+ peers = /* @__PURE__ */ new Map();
22741
+ /** Active WebSocket connections (peerId → WebSocket) */
22742
+ connections = /* @__PURE__ */ new Map();
22743
+ /** Pending inference request callbacks (msgId → resolver) */
22744
+ pendingRequests = /* @__PURE__ */ new Map();
22745
+ server = null;
22746
+ wss = null;
22747
+ pingTimer = null;
22748
+ gossipTimer = null;
22749
+ _port = 0;
22750
+ _authKey;
22751
+ capabilities;
22752
+ displayName;
22753
+ get port() {
22754
+ return this._port;
22755
+ }
22756
+ get authKey() {
22757
+ return this._authKey;
22758
+ }
22759
+ get peerCount() {
22760
+ return this.peers.size;
22761
+ }
22762
+ get connectedCount() {
22763
+ return this.connections.size;
22764
+ }
22765
+ constructor(options) {
22766
+ super();
22767
+ this.options = options;
22768
+ const { publicKey, privateKey } = generateKeyPairSync("ed25519");
22769
+ this.publicKey = publicKey.export({ type: "spki", format: "der" });
22770
+ this.privateKey = privateKey.export({ type: "pkcs8", format: "der" });
22771
+ this.peerId = createHash2("sha256").update(this.publicKey).digest("base64url").slice(0, 22);
22772
+ this.capabilities = options.capabilities;
22773
+ this.displayName = options.displayName;
22774
+ this._authKey = options.authKey ?? randomBytes7(24).toString("base64url");
22775
+ }
22776
+ // ── Lifecycle ───────────────────────────────────────────────────────────
22777
+ /** Start the mesh node — creates WS server and connects to bootstrap peers */
22778
+ async start() {
22779
+ const port = await this.findFreePort();
22780
+ this._port = port;
22781
+ this.server = createServer3();
22782
+ this.wss = new import_websocket_server.default({ server: this.server });
22783
+ this.wss.on("connection", (ws, req) => {
22784
+ this.handleInboundConnection(ws, req.url ?? "");
22785
+ });
22786
+ await new Promise((resolve28, reject) => {
22787
+ this.server.listen(port, "127.0.0.1", () => resolve28());
22788
+ this.server.on("error", reject);
22789
+ });
22790
+ this.pingTimer = setInterval(() => this.pingAll(), PING_INTERVAL_MS);
22791
+ this.gossipTimer = setInterval(() => this.gossip(), GOSSIP_INTERVAL_MS);
22792
+ if (this.options.bootstrapPeers) {
22793
+ for (const endpoint of this.options.bootstrapPeers) {
22794
+ this.connectToPeer(endpoint).catch(() => {
22795
+ });
22796
+ }
22797
+ }
22798
+ this.emit("started", port);
22799
+ return port;
22800
+ }
22801
+ /** Stop the mesh node */
22802
+ async stop() {
22803
+ if (this.pingTimer) {
22804
+ clearInterval(this.pingTimer);
22805
+ this.pingTimer = null;
22806
+ }
22807
+ if (this.gossipTimer) {
22808
+ clearInterval(this.gossipTimer);
22809
+ this.gossipTimer = null;
22810
+ }
22811
+ for (const [peerId, ws] of this.connections) {
22812
+ this.sendMsg(ws, "bye", {});
22813
+ ws.close(1e3, "shutting down");
22814
+ }
22815
+ this.connections.clear();
22816
+ for (const [, pending] of this.pendingRequests) {
22817
+ clearTimeout(pending.timeout);
22818
+ pending.reject(new Error("Mesh shutting down"));
22819
+ }
22820
+ this.pendingRequests.clear();
22821
+ if (this.wss) {
22822
+ this.wss.close();
22823
+ this.wss = null;
22824
+ }
22825
+ if (this.server) {
22826
+ await new Promise((resolve28) => this.server.close(() => resolve28()));
22827
+ this.server = null;
22828
+ }
22829
+ this.emit("stopped");
22830
+ }
22831
+ // ── Peer connections ────────────────────────────────────────────────────
22832
+ /** Connect to a remote peer by endpoint URL */
22833
+ async connectToPeer(endpoint, trust = "public") {
22834
+ let wsUrl = endpoint;
22835
+ if (wsUrl.startsWith("https://"))
22836
+ wsUrl = wsUrl.replace("https://", "wss://");
22837
+ else if (wsUrl.startsWith("http://"))
22838
+ wsUrl = wsUrl.replace("http://", "ws://");
22839
+ else if (!wsUrl.startsWith("ws://") && !wsUrl.startsWith("wss://"))
22840
+ wsUrl = `ws://${wsUrl}`;
22841
+ if (!wsUrl.includes("/p2p"))
22842
+ wsUrl += "/p2p";
22843
+ wsUrl += `?key=${encodeURIComponent(this._authKey)}`;
22844
+ return new Promise((resolve28, reject) => {
22845
+ const ws = new import_websocket.default(wsUrl, { handshakeTimeout: 1e4 });
22846
+ let resolved = false;
22847
+ const timeout = setTimeout(() => {
22848
+ if (!resolved) {
22849
+ resolved = true;
22850
+ ws.close();
22851
+ reject(new Error("Connection timeout"));
22852
+ }
22853
+ }, 15e3);
22854
+ ws.on("open", () => {
22855
+ this.sendMsg(ws, "hello", {
22856
+ peerId: this.peerId,
22857
+ displayName: this.displayName,
22858
+ capabilities: this.capabilities,
22859
+ endpoint,
22860
+ publicKey: this.publicKey.toString("base64url")
22861
+ });
22862
+ });
22863
+ ws.on("message", (data) => {
22864
+ try {
22865
+ const msg = JSON.parse(data.toString());
22866
+ if (msg.type === "hello_ack" && !resolved) {
22867
+ resolved = true;
22868
+ clearTimeout(timeout);
22869
+ const payload = msg.payload;
22870
+ const peer = {
22871
+ peerId: payload.peerId,
22872
+ displayName: payload.displayName,
22873
+ endpoint,
22874
+ trust,
22875
+ capabilities: payload.capabilities,
22876
+ lastSeen: (/* @__PURE__ */ new Date()).toISOString(),
22877
+ latencyMs: 0,
22878
+ connected: true
22879
+ };
22880
+ this.peers.set(peer.peerId, peer);
22881
+ this.connections.set(peer.peerId, ws);
22882
+ this.setupPeerHandlers(ws, peer.peerId);
22883
+ this.emit("peer_connected", peer);
22884
+ resolve28(peer);
22885
+ } else {
22886
+ this.handleMessage(msg, ws);
22887
+ }
22888
+ } catch {
22889
+ }
22890
+ });
22891
+ ws.on("error", (err) => {
22892
+ if (!resolved) {
22893
+ resolved = true;
22894
+ clearTimeout(timeout);
22895
+ reject(err);
22896
+ }
22897
+ });
22898
+ ws.on("close", () => {
22899
+ if (!resolved) {
22900
+ resolved = true;
22901
+ clearTimeout(timeout);
22902
+ reject(new Error("Connection closed before handshake"));
22903
+ }
22904
+ });
22905
+ });
22906
+ }
22907
+ /** Set the trust tier for a known peer */
22908
+ setTrust(peerId, trust) {
22909
+ const peer = this.peers.get(peerId);
22910
+ if (peer) {
22911
+ peer.trust = trust;
22912
+ return true;
22913
+ }
22914
+ return false;
22915
+ }
22916
+ /** Get all known peers */
22917
+ getPeers() {
22918
+ return Array.from(this.peers.values());
22919
+ }
22920
+ /** Get connected peers */
22921
+ getConnectedPeers() {
22922
+ return Array.from(this.peers.values()).filter((p) => p.connected);
22923
+ }
22924
+ /** Find peers that can serve a given model */
22925
+ findPeersForModel(model) {
22926
+ return this.getConnectedPeers().filter((p) => p.capabilities.models.some((m) => m === model || m.startsWith(model)));
22927
+ }
22928
+ // ── Inference routing ──────────────────────────────────────────────────
22929
+ /**
22930
+ * Send an inference request to a specific peer.
22931
+ * Returns a promise that resolves when the response arrives.
22932
+ */
22933
+ async requestInference(peerId, request, timeoutMs = 12e4) {
22934
+ const ws = this.connections.get(peerId);
22935
+ if (!ws || ws.readyState !== import_websocket.default.OPEN) {
22936
+ throw new Error(`Peer ${peerId} not connected`);
22937
+ }
22938
+ const msgId = randomBytes7(8).toString("hex");
22939
+ return new Promise((resolve28, reject) => {
22940
+ const timeout = setTimeout(() => {
22941
+ this.pendingRequests.delete(msgId);
22942
+ reject(new Error(`Inference timeout (${timeoutMs}ms)`));
22943
+ }, timeoutMs);
22944
+ this.pendingRequests.set(msgId, { resolve: resolve28, reject, timeout, chunks: [] });
22945
+ this.sendMsg(ws, "infer_request", request, msgId);
22946
+ });
22947
+ }
22948
+ // ── Inbound connection handling ─────────────────────────────────────────
22949
+ handleInboundConnection(ws, url) {
22950
+ const urlObj = new URL(url, "http://localhost");
22951
+ const key = urlObj.searchParams.get("key");
22952
+ if (key !== this._authKey) {
22953
+ ws.close(4001, "Unauthorized");
22954
+ return;
22955
+ }
22956
+ const helloTimeout = setTimeout(() => ws.close(4002, "Hello timeout"), 1e4);
22957
+ ws.once("message", (data) => {
22958
+ clearTimeout(helloTimeout);
22959
+ try {
22960
+ const msg = JSON.parse(data.toString());
22961
+ if (msg.type !== "hello") {
22962
+ ws.close(4003, "Expected hello");
22963
+ return;
22964
+ }
22965
+ const payload = msg.payload;
22966
+ if (payload.peerId === this.peerId) {
22967
+ ws.close(4004, "Self-connection");
22968
+ return;
22969
+ }
22970
+ const peer = {
22971
+ peerId: payload.peerId,
22972
+ displayName: payload.displayName,
22973
+ endpoint: payload.endpoint,
22974
+ trust: "public",
22975
+ // inbound connections start at public trust
22976
+ capabilities: payload.capabilities,
22977
+ lastSeen: (/* @__PURE__ */ new Date()).toISOString(),
22978
+ latencyMs: 0,
22979
+ connected: true
22980
+ };
22981
+ const existing = this.peers.get(peer.peerId);
22982
+ if (existing) {
22983
+ peer.trust = existing.trust;
22984
+ }
22985
+ this.peers.set(peer.peerId, peer);
22986
+ this.connections.set(peer.peerId, ws);
22987
+ this.sendMsg(ws, "hello_ack", {
22988
+ peerId: this.peerId,
22989
+ displayName: this.displayName,
22990
+ capabilities: this.capabilities
22991
+ });
22992
+ this.setupPeerHandlers(ws, peer.peerId);
22993
+ this.emit("peer_connected", peer);
22994
+ } catch {
22995
+ ws.close(4005, "Malformed hello");
22996
+ }
22997
+ });
22998
+ }
22999
+ setupPeerHandlers(ws, peerId) {
23000
+ ws.on("message", (data) => {
23001
+ try {
23002
+ const msg = JSON.parse(data.toString());
23003
+ this.handleMessage(msg, ws);
23004
+ } catch {
23005
+ }
23006
+ });
23007
+ ws.on("close", () => {
23008
+ this.connections.delete(peerId);
23009
+ const peer = this.peers.get(peerId);
23010
+ if (peer) {
23011
+ peer.connected = false;
23012
+ this.emit("peer_disconnected", peer);
23013
+ }
23014
+ });
23015
+ ws.on("error", () => {
23016
+ this.connections.delete(peerId);
23017
+ const peer = this.peers.get(peerId);
23018
+ if (peer) {
23019
+ peer.connected = false;
23020
+ this.emit("peer_error", peer);
23021
+ }
23022
+ });
23023
+ }
23024
+ // ── Message handling ────────────────────────────────────────────────────
23025
+ handleMessage(msg, ws) {
23026
+ const peer = this.peers.get(msg.from);
23027
+ if (peer)
23028
+ peer.lastSeen = (/* @__PURE__ */ new Date()).toISOString();
23029
+ switch (msg.type) {
23030
+ case "ping":
23031
+ this.sendMsg(ws, "pong", { ts: msg.ts }, msg.msgId);
23032
+ break;
23033
+ case "pong":
23034
+ if (peer) {
23035
+ const sent = new Date(msg.ts).getTime();
23036
+ peer.latencyMs = Date.now() - sent;
23037
+ }
23038
+ break;
23039
+ case "capabilities":
23040
+ if (peer) {
23041
+ peer.capabilities = msg.payload;
23042
+ this.emit("peer_capabilities", peer);
23043
+ }
23044
+ break;
23045
+ case "peer_list": {
23046
+ const peerList = msg.payload;
23047
+ for (const p of peerList) {
23048
+ if (p.peerId === this.peerId)
23049
+ continue;
23050
+ if (this.peers.has(p.peerId))
23051
+ continue;
23052
+ if (this.peers.size >= MAX_PEERS)
23053
+ break;
23054
+ this.connectToPeer(p.endpoint).catch(() => {
23055
+ });
23056
+ }
23057
+ break;
23058
+ }
23059
+ case "infer_request":
23060
+ this.emit("infer_request", {
23061
+ from: msg.from,
23062
+ msgId: msg.msgId,
23063
+ request: msg.payload,
23064
+ respond: (response) => {
23065
+ this.sendMsg(ws, "infer_response", response, msg.msgId);
23066
+ },
23067
+ error: (error) => {
23068
+ this.sendMsg(ws, "infer_error", { error }, msg.msgId);
23069
+ }
23070
+ });
23071
+ break;
23072
+ case "infer_response": {
23073
+ const pending = this.pendingRequests.get(msg.msgId);
23074
+ if (pending) {
23075
+ clearTimeout(pending.timeout);
23076
+ this.pendingRequests.delete(msg.msgId);
23077
+ pending.resolve(msg.payload);
23078
+ }
23079
+ break;
23080
+ }
23081
+ case "infer_stream": {
23082
+ const streamPending = this.pendingRequests.get(msg.msgId);
23083
+ if (streamPending) {
23084
+ const chunk = msg.payload;
23085
+ streamPending.chunks.push(chunk.content);
23086
+ this.emit("infer_stream", { msgId: msg.msgId, chunk });
23087
+ if (chunk.done) {
23088
+ clearTimeout(streamPending.timeout);
23089
+ this.pendingRequests.delete(msg.msgId);
23090
+ streamPending.resolve({
23091
+ content: streamPending.chunks.join(""),
23092
+ usage: chunk.usage,
23093
+ done: true
23094
+ });
23095
+ }
23096
+ }
23097
+ break;
23098
+ }
23099
+ case "infer_error": {
23100
+ const errPending = this.pendingRequests.get(msg.msgId);
23101
+ if (errPending) {
23102
+ clearTimeout(errPending.timeout);
23103
+ this.pendingRequests.delete(msg.msgId);
23104
+ const payload = msg.payload;
23105
+ errPending.reject(new Error(payload.error));
23106
+ }
23107
+ break;
23108
+ }
23109
+ case "bye":
23110
+ ws.close(1e3, "Peer disconnected");
23111
+ break;
23112
+ default:
23113
+ break;
23114
+ }
23115
+ }
23116
+ // ── Heartbeat & gossip ─────────────────────────────────────────────────
23117
+ pingAll() {
23118
+ const now = Date.now();
23119
+ for (const [peerId, ws] of this.connections) {
23120
+ if (ws.readyState === import_websocket.default.OPEN) {
23121
+ this.sendMsg(ws, "ping", { ts: (/* @__PURE__ */ new Date()).toISOString() });
23122
+ }
23123
+ const peer = this.peers.get(peerId);
23124
+ if (peer) {
23125
+ const lastSeen = new Date(peer.lastSeen).getTime();
23126
+ if (now - lastSeen > PEER_TIMEOUT_MS) {
23127
+ peer.connected = false;
23128
+ ws.close(4006, "Timeout");
23129
+ this.connections.delete(peerId);
23130
+ this.emit("peer_timeout", peer);
23131
+ }
23132
+ }
23133
+ }
23134
+ }
23135
+ gossip() {
23136
+ const peerList = Array.from(this.peers.values()).filter((p) => p.connected).map((p) => ({ endpoint: p.endpoint, peerId: p.peerId, displayName: p.displayName }));
23137
+ for (const [, ws] of this.connections) {
23138
+ if (ws.readyState === import_websocket.default.OPEN) {
23139
+ this.sendMsg(ws, "peer_list", peerList);
23140
+ }
23141
+ }
23142
+ }
23143
+ /** Update this node's capabilities and broadcast to all peers */
23144
+ updateCapabilities(caps) {
23145
+ Object.assign(this.capabilities, caps);
23146
+ for (const [, ws] of this.connections) {
23147
+ if (ws.readyState === import_websocket.default.OPEN) {
23148
+ this.sendMsg(ws, "capabilities", this.capabilities);
23149
+ }
23150
+ }
23151
+ }
23152
+ // ── Helpers ─────────────────────────────────────────────────────────────
23153
+ sendMsg(ws, type, payload, msgId) {
23154
+ if (ws.readyState !== import_websocket.default.OPEN)
23155
+ return;
23156
+ const msg = {
23157
+ type,
23158
+ from: this.peerId,
23159
+ msgId: msgId ?? randomBytes7(8).toString("hex"),
23160
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
23161
+ payload
23162
+ };
23163
+ ws.send(JSON.stringify(msg));
23164
+ }
23165
+ findFreePort() {
23166
+ return new Promise((resolve28, reject) => {
23167
+ const srv = createServer3();
23168
+ srv.listen(0, "127.0.0.1", () => {
23169
+ const addr = srv.address();
23170
+ if (addr && typeof addr === "object") {
23171
+ const port = addr.port;
23172
+ srv.close(() => resolve28(port));
23173
+ } else {
23174
+ srv.close(() => reject(new Error("Could not find free port")));
23175
+ }
23176
+ });
23177
+ srv.on("error", reject);
23178
+ });
23179
+ }
23180
+ };
23181
+ }
23182
+ });
23183
+
23184
+ // packages/cli/dist/tui/p2p/inference-router.js
23185
+ import { EventEmitter as EventEmitter5 } from "node:events";
23186
+ var TRUST_WEIGHTS, InferenceRouter;
23187
+ var init_inference_router = __esm({
23188
+ "packages/cli/dist/tui/p2p/inference-router.js"() {
23189
+ "use strict";
23190
+ init_types();
23191
+ TRUST_WEIGHTS = {
23192
+ local: 10,
23193
+ tee: 8,
23194
+ verified: 5,
23195
+ public: 2
23196
+ };
23197
+ InferenceRouter = class extends EventEmitter5 {
23198
+ mesh;
23199
+ vault;
23200
+ defaultTimeoutMs;
23201
+ minTrust;
23202
+ /** Stats */
23203
+ _totalRouted = 0;
23204
+ _totalRedacted = 0;
23205
+ _totalErrors = 0;
23206
+ get totalRouted() {
23207
+ return this._totalRouted;
23208
+ }
23209
+ get totalRedacted() {
23210
+ return this._totalRedacted;
23211
+ }
23212
+ get totalErrors() {
23213
+ return this._totalErrors;
23214
+ }
23215
+ constructor(options) {
23216
+ super();
23217
+ this.mesh = options.mesh;
23218
+ this.vault = options.vault;
23219
+ this.defaultTimeoutMs = options.defaultTimeoutMs ?? 12e4;
23220
+ this.minTrust = options.minTrust ?? "public";
23221
+ if (options.requireAuth) {
23222
+ this.minTrust = "verified";
23223
+ }
23224
+ }
23225
+ // ── Route discovery ─────────────────────────────────────────────────────
23226
+ /**
23227
+ * Find all available routes for a given model, ranked by score.
23228
+ */
23229
+ findRoutes(model) {
23230
+ const peers = this.mesh.findPeersForModel(model);
23231
+ const minLevel = trustLevel(this.minTrust);
23232
+ return peers.filter((p) => trustLevel(p.trust) >= minLevel).map((p) => this.scoreRoute(p, model)).sort((a, b) => b.score - a.score);
23233
+ }
23234
+ /**
23235
+ * Find the best route for a given model.
23236
+ */
23237
+ bestRoute(model) {
23238
+ const routes = this.findRoutes(model);
23239
+ return routes.length > 0 ? routes[0] : null;
23240
+ }
23241
+ // ── Inference ──────────────────────────────────────────────────────────
23242
+ /**
23243
+ * Route an inference request to the best available peer.
23244
+ *
23245
+ * This is the main entry point. It handles:
23246
+ * 1. Peer selection
23247
+ * 2. Secret redaction based on peer trust
23248
+ * 3. Request dispatch
23249
+ * 4. Secret injection on response
23250
+ */
23251
+ async infer(model, messages, options) {
23252
+ let peer;
23253
+ if (options?.peerId) {
23254
+ const found = this.mesh.getPeers().find((p) => p.peerId === options.peerId);
23255
+ if (!found || !found.connected) {
23256
+ throw new Error(`Peer ${options.peerId} not available`);
23257
+ }
23258
+ peer = found;
23259
+ } else {
23260
+ const minTrust = options?.minTrust ?? this.minTrust;
23261
+ const routes = this.findRoutes(model);
23262
+ const filtered = routes.filter((r) => trustLevel(r.trust) >= trustLevel(minTrust));
23263
+ if (filtered.length === 0) {
23264
+ throw new Error(`No available peers for model "${model}" at trust >= ${minTrust}`);
23265
+ }
23266
+ peer = filtered[0].peer;
23267
+ }
23268
+ const requiresRedaction = trustLevel(peer.trust) < trustLevel("local");
23269
+ let redactedCount = 0;
23270
+ const redactedMessages = messages.map((msg) => {
23271
+ const original = msg.content;
23272
+ const redacted = requiresRedaction ? this.vault.redact(original, peer.trust) : original;
23273
+ if (redacted !== original)
23274
+ redactedCount++;
23275
+ return { ...msg, content: redacted };
23276
+ });
23277
+ let redactedTools = options?.tools;
23278
+ if (requiresRedaction && redactedTools) {
23279
+ redactedTools = redactedTools.map((t) => ({
23280
+ ...t,
23281
+ function: {
23282
+ ...t.function,
23283
+ description: this.vault.redact(t.function.description, peer.trust)
23284
+ }
23285
+ }));
23286
+ }
23287
+ const request = {
23288
+ model,
23289
+ messages: redactedMessages,
23290
+ tools: redactedTools,
23291
+ temperature: options?.temperature,
23292
+ maxTokens: options?.maxTokens,
23293
+ stream: options?.stream
23294
+ };
23295
+ this.emit("routing", { peer, redactedCount, model });
23296
+ this._totalRouted++;
23297
+ if (redactedCount > 0)
23298
+ this._totalRedacted++;
23299
+ try {
23300
+ const response = await this.mesh.requestInference(peer.peerId, request, options?.timeoutMs ?? this.defaultTimeoutMs);
23301
+ const injectedContent = this.vault.inject(response.content);
23302
+ const injectedToolCalls = response.toolCalls?.map((tc) => ({
23303
+ ...tc,
23304
+ function: {
23305
+ ...tc.function,
23306
+ arguments: this.vault.inject(tc.function.arguments)
23307
+ }
23308
+ }));
23309
+ const result = {
23310
+ content: injectedContent,
23311
+ toolCalls: injectedToolCalls,
23312
+ usage: response.usage,
23313
+ peer,
23314
+ wasRedacted: redactedCount > 0,
23315
+ redactedCount
23316
+ };
23317
+ this.emit("completed", result);
23318
+ return result;
23319
+ } catch (err) {
23320
+ this._totalErrors++;
23321
+ this.emit("error", { peer, error: err });
23322
+ throw err;
23323
+ }
23324
+ }
23325
+ /**
23326
+ * Handle an inbound inference request from a peer.
23327
+ * Called by the mesh node when it receives an infer_request.
23328
+ *
23329
+ * This is the "server side" — we perform inference locally and return results.
23330
+ * The response text may contain placeholders from the requester's vault,
23331
+ * which is fine — we don't have their secrets and don't need them.
23332
+ */
23333
+ async handleInboundRequest(request, inferFn) {
23334
+ for (const msg of request.messages) {
23335
+ const found = this.vault.scan(msg.content);
23336
+ if (found.length > 0) {
23337
+ this.emit("leaked_secrets_detected", {
23338
+ count: found.length,
23339
+ patterns: found.map((f) => f.slice(0, 4) + "***")
23340
+ });
23341
+ }
23342
+ }
23343
+ return inferFn(request);
23344
+ }
23345
+ // ── Scoring ────────────────────────────────────────────────────────────
23346
+ scoreRoute(peer, model) {
23347
+ const trust = peer.trust;
23348
+ const trustWeight = TRUST_WEIGHTS[trust];
23349
+ const latencyFactor = 1 / (1 + peer.latencyMs / 100);
23350
+ const loadFactor = 1 - peer.capabilities.load;
23351
+ const modelExact = peer.capabilities.models.includes(model) ? 1 : 0.8;
23352
+ const score = trustWeight * latencyFactor * loadFactor * modelExact;
23353
+ return {
23354
+ peer,
23355
+ trust,
23356
+ latencyMs: peer.latencyMs,
23357
+ requiresRedaction: trustLevel(trust) < trustLevel("local"),
23358
+ score
23359
+ };
23360
+ }
23361
+ // ── Utility ────────────────────────────────────────────────────────────
23362
+ /** Get a summary of routing statistics */
23363
+ getStats() {
23364
+ const peers = this.mesh.getConnectedPeers();
23365
+ const models = /* @__PURE__ */ new Set();
23366
+ for (const p of peers) {
23367
+ for (const m of p.capabilities.models)
23368
+ models.add(m);
23369
+ }
23370
+ return {
23371
+ totalRouted: this._totalRouted,
23372
+ totalRedacted: this._totalRedacted,
23373
+ totalErrors: this._totalErrors,
23374
+ connectedPeers: peers.length,
23375
+ availableModels: Array.from(models)
23376
+ };
23377
+ }
23378
+ };
23379
+ }
23380
+ });
23381
+
23382
+ // packages/cli/dist/tui/p2p/index.js
23383
+ var init_p2p = __esm({
23384
+ "packages/cli/dist/tui/p2p/index.js"() {
23385
+ "use strict";
23386
+ init_secret_vault();
23387
+ init_peer_mesh();
23388
+ init_inference_router();
23389
+ init_types();
23390
+ }
23391
+ });
23392
+
23393
+ // packages/cli/dist/tui/call-agent.js
23394
+ import { EventEmitter as EventEmitter6 } from "node:events";
22445
23395
  import crypto2 from "node:crypto";
22446
23396
  function adaptTool(tool) {
22447
23397
  return {
@@ -22501,7 +23451,7 @@ var init_call_agent = __esm({
22501
23451
  }
22502
23452
  };
22503
23453
  _globalFeed = null;
22504
- CallSubAgent = class extends EventEmitter4 {
23454
+ CallSubAgent = class extends EventEmitter6 {
22505
23455
  tier;
22506
23456
  clientId;
22507
23457
  runner = null;
@@ -23102,7 +24052,7 @@ Approach this task thoughtfully:
23102
24052
  });
23103
24053
 
23104
24054
  // packages/prompts/dist/index.js
23105
- import { join as join29, dirname as dirname11 } from "node:path";
24055
+ import { join as join30, dirname as dirname12 } from "node:path";
23106
24056
  import { fileURLToPath as fileURLToPath8 } from "node:url";
23107
24057
  var _dir, _packageRoot;
23108
24058
  var init_dist6 = __esm({
@@ -23112,27 +24062,27 @@ var init_dist6 = __esm({
23112
24062
  init_render2();
23113
24063
  init_task_templates();
23114
24064
  init_render2();
23115
- _dir = dirname11(fileURLToPath8(import.meta.url));
23116
- _packageRoot = join29(_dir, "..");
24065
+ _dir = dirname12(fileURLToPath8(import.meta.url));
24066
+ _packageRoot = join30(_dir, "..");
23117
24067
  }
23118
24068
  });
23119
24069
 
23120
24070
  // packages/cli/dist/tui/oa-directory.js
23121
- import { existsSync as existsSync21, mkdirSync as mkdirSync7, readFileSync as readFileSync15, writeFileSync as writeFileSync7, readdirSync as readdirSync7, statSync as statSync9, unlinkSync as unlinkSync3 } from "node:fs";
23122
- import { join as join30, relative as relative2, basename as basename9, extname as extname8 } from "node:path";
24071
+ import { existsSync as existsSync22, mkdirSync as mkdirSync8, readFileSync as readFileSync16, writeFileSync as writeFileSync8, readdirSync as readdirSync7, statSync as statSync9, unlinkSync as unlinkSync3 } from "node:fs";
24072
+ import { join as join31, relative as relative2, basename as basename9, extname as extname8 } from "node:path";
23123
24073
  import { homedir as homedir9 } from "node:os";
23124
24074
  function initOaDirectory(repoRoot) {
23125
- const oaPath = join30(repoRoot, OA_DIR);
24075
+ const oaPath = join31(repoRoot, OA_DIR);
23126
24076
  for (const sub of SUBDIRS) {
23127
- mkdirSync7(join30(oaPath, sub), { recursive: true });
24077
+ mkdirSync8(join31(oaPath, sub), { recursive: true });
23128
24078
  }
23129
24079
  try {
23130
- const gitignorePath = join30(repoRoot, ".gitignore");
24080
+ const gitignorePath = join31(repoRoot, ".gitignore");
23131
24081
  const settingsPattern = ".oa/settings.json";
23132
- if (existsSync21(gitignorePath)) {
23133
- const content = readFileSync15(gitignorePath, "utf-8");
24082
+ if (existsSync22(gitignorePath)) {
24083
+ const content = readFileSync16(gitignorePath, "utf-8");
23134
24084
  if (!content.includes(settingsPattern)) {
23135
- writeFileSync7(gitignorePath, content.trimEnd() + "\n" + settingsPattern + "\n", "utf-8");
24085
+ writeFileSync8(gitignorePath, content.trimEnd() + "\n" + settingsPattern + "\n", "utf-8");
23136
24086
  }
23137
24087
  }
23138
24088
  } catch {
@@ -23140,41 +24090,41 @@ function initOaDirectory(repoRoot) {
23140
24090
  return oaPath;
23141
24091
  }
23142
24092
  function hasOaDirectory(repoRoot) {
23143
- return existsSync21(join30(repoRoot, OA_DIR, "index"));
24093
+ return existsSync22(join31(repoRoot, OA_DIR, "index"));
23144
24094
  }
23145
24095
  function loadProjectSettings(repoRoot) {
23146
- const settingsPath = join30(repoRoot, OA_DIR, "settings.json");
24096
+ const settingsPath = join31(repoRoot, OA_DIR, "settings.json");
23147
24097
  try {
23148
- if (existsSync21(settingsPath)) {
23149
- return JSON.parse(readFileSync15(settingsPath, "utf-8"));
24098
+ if (existsSync22(settingsPath)) {
24099
+ return JSON.parse(readFileSync16(settingsPath, "utf-8"));
23150
24100
  }
23151
24101
  } catch {
23152
24102
  }
23153
24103
  return {};
23154
24104
  }
23155
24105
  function saveProjectSettings(repoRoot, settings) {
23156
- const oaPath = join30(repoRoot, OA_DIR);
23157
- mkdirSync7(oaPath, { recursive: true });
24106
+ const oaPath = join31(repoRoot, OA_DIR);
24107
+ mkdirSync8(oaPath, { recursive: true });
23158
24108
  const existing = loadProjectSettings(repoRoot);
23159
24109
  const merged = { ...existing, ...settings };
23160
- writeFileSync7(join30(oaPath, "settings.json"), JSON.stringify(merged, null, 2) + "\n", { encoding: "utf-8", mode: 384 });
24110
+ writeFileSync8(join31(oaPath, "settings.json"), JSON.stringify(merged, null, 2) + "\n", { encoding: "utf-8", mode: 384 });
23161
24111
  }
23162
24112
  function loadGlobalSettings() {
23163
- const settingsPath = join30(homedir9(), ".open-agents", "settings.json");
24113
+ const settingsPath = join31(homedir9(), ".open-agents", "settings.json");
23164
24114
  try {
23165
- if (existsSync21(settingsPath)) {
23166
- return JSON.parse(readFileSync15(settingsPath, "utf-8"));
24115
+ if (existsSync22(settingsPath)) {
24116
+ return JSON.parse(readFileSync16(settingsPath, "utf-8"));
23167
24117
  }
23168
24118
  } catch {
23169
24119
  }
23170
24120
  return {};
23171
24121
  }
23172
24122
  function saveGlobalSettings(settings) {
23173
- const dir = join30(homedir9(), ".open-agents");
23174
- mkdirSync7(dir, { recursive: true });
24123
+ const dir = join31(homedir9(), ".open-agents");
24124
+ mkdirSync8(dir, { recursive: true });
23175
24125
  const existing = loadGlobalSettings();
23176
24126
  const merged = { ...existing, ...settings };
23177
- writeFileSync7(join30(dir, "settings.json"), JSON.stringify(merged, null, 2) + "\n", { encoding: "utf-8", mode: 384 });
24127
+ writeFileSync8(join31(dir, "settings.json"), JSON.stringify(merged, null, 2) + "\n", { encoding: "utf-8", mode: 384 });
23178
24128
  }
23179
24129
  function resolveSettings(repoRoot) {
23180
24130
  const global = loadGlobalSettings();
@@ -23189,12 +24139,12 @@ function discoverContextFiles(repoRoot, maxContentLen = 8e3) {
23189
24139
  while (dir && !visited.has(dir)) {
23190
24140
  visited.add(dir);
23191
24141
  for (const name of CONTEXT_FILES) {
23192
- const filePath = join30(dir, name);
24142
+ const filePath = join31(dir, name);
23193
24143
  const normalizedName = name.toLowerCase();
23194
- if (existsSync21(filePath) && !seen.has(filePath)) {
24144
+ if (existsSync22(filePath) && !seen.has(filePath)) {
23195
24145
  seen.add(filePath);
23196
24146
  try {
23197
- let content = readFileSync15(filePath, "utf-8");
24147
+ let content = readFileSync16(filePath, "utf-8");
23198
24148
  if (content.length > maxContentLen) {
23199
24149
  content = content.slice(0, maxContentLen) + "\n\n...(truncated)";
23200
24150
  }
@@ -23208,11 +24158,11 @@ function discoverContextFiles(repoRoot, maxContentLen = 8e3) {
23208
24158
  }
23209
24159
  }
23210
24160
  }
23211
- const projectMap = join30(dir, OA_DIR, "context", "project-map.md");
23212
- if (existsSync21(projectMap) && !seen.has(projectMap)) {
24161
+ const projectMap = join31(dir, OA_DIR, "context", "project-map.md");
24162
+ if (existsSync22(projectMap) && !seen.has(projectMap)) {
23213
24163
  seen.add(projectMap);
23214
24164
  try {
23215
- let content = readFileSync15(projectMap, "utf-8");
24165
+ let content = readFileSync16(projectMap, "utf-8");
23216
24166
  if (content.length > maxContentLen) {
23217
24167
  content = content.slice(0, maxContentLen) + "\n\n...(truncated)";
23218
24168
  }
@@ -23224,7 +24174,7 @@ function discoverContextFiles(repoRoot, maxContentLen = 8e3) {
23224
24174
  } catch {
23225
24175
  }
23226
24176
  }
23227
- const parent = join30(dir, "..");
24177
+ const parent = join31(dir, "..");
23228
24178
  if (parent === dir)
23229
24179
  break;
23230
24180
  dir = parent;
@@ -23242,9 +24192,9 @@ function discoverContextFiles(repoRoot, maxContentLen = 8e3) {
23242
24192
  return found;
23243
24193
  }
23244
24194
  function readIndexMeta(repoRoot) {
23245
- const metaPath = join30(repoRoot, OA_DIR, "index", "meta.json");
24195
+ const metaPath = join31(repoRoot, OA_DIR, "index", "meta.json");
23246
24196
  try {
23247
- return JSON.parse(readFileSync15(metaPath, "utf-8"));
24197
+ return JSON.parse(readFileSync16(metaPath, "utf-8"));
23248
24198
  } catch {
23249
24199
  return null;
23250
24200
  }
@@ -23295,28 +24245,28 @@ ${tree}\`\`\`
23295
24245
  sections.push("");
23296
24246
  }
23297
24247
  const content = sections.join("\n");
23298
- const contextDir = join30(repoRoot, OA_DIR, "context");
23299
- mkdirSync7(contextDir, { recursive: true });
23300
- writeFileSync7(join30(contextDir, "project-map.md"), content, "utf-8");
24248
+ const contextDir = join31(repoRoot, OA_DIR, "context");
24249
+ mkdirSync8(contextDir, { recursive: true });
24250
+ writeFileSync8(join31(contextDir, "project-map.md"), content, "utf-8");
23301
24251
  return content;
23302
24252
  }
23303
24253
  function saveSession(repoRoot, session) {
23304
- const historyDir = join30(repoRoot, OA_DIR, "history");
23305
- mkdirSync7(historyDir, { recursive: true });
23306
- writeFileSync7(join30(historyDir, `${session.id}.json`), JSON.stringify(session, null, 2), "utf-8");
24254
+ const historyDir = join31(repoRoot, OA_DIR, "history");
24255
+ mkdirSync8(historyDir, { recursive: true });
24256
+ writeFileSync8(join31(historyDir, `${session.id}.json`), JSON.stringify(session, null, 2), "utf-8");
23307
24257
  }
23308
24258
  function loadRecentSessions(repoRoot, limit = 5) {
23309
- const historyDir = join30(repoRoot, OA_DIR, "history");
23310
- if (!existsSync21(historyDir))
24259
+ const historyDir = join31(repoRoot, OA_DIR, "history");
24260
+ if (!existsSync22(historyDir))
23311
24261
  return [];
23312
24262
  try {
23313
24263
  const files = readdirSync7(historyDir).filter((f) => f.endsWith(".json") && f !== "pending-task.json").map((f) => {
23314
- const stat5 = statSync9(join30(historyDir, f));
24264
+ const stat5 = statSync9(join31(historyDir, f));
23315
24265
  return { file: f, mtime: stat5.mtimeMs };
23316
24266
  }).sort((a, b) => b.mtime - a.mtime).slice(0, limit);
23317
24267
  return files.map((f) => {
23318
24268
  try {
23319
- return JSON.parse(readFileSync15(join30(historyDir, f.file), "utf-8"));
24269
+ return JSON.parse(readFileSync16(join31(historyDir, f.file), "utf-8"));
23320
24270
  } catch {
23321
24271
  return null;
23322
24272
  }
@@ -23326,16 +24276,16 @@ function loadRecentSessions(repoRoot, limit = 5) {
23326
24276
  }
23327
24277
  }
23328
24278
  function savePendingTask(repoRoot, task) {
23329
- const historyDir = join30(repoRoot, OA_DIR, "history");
23330
- mkdirSync7(historyDir, { recursive: true });
23331
- writeFileSync7(join30(historyDir, PENDING_TASK_FILE), JSON.stringify(task, null, 2) + "\n", "utf-8");
24279
+ const historyDir = join31(repoRoot, OA_DIR, "history");
24280
+ mkdirSync8(historyDir, { recursive: true });
24281
+ writeFileSync8(join31(historyDir, PENDING_TASK_FILE), JSON.stringify(task, null, 2) + "\n", "utf-8");
23332
24282
  }
23333
24283
  function loadPendingTask(repoRoot) {
23334
- const filePath = join30(repoRoot, OA_DIR, "history", PENDING_TASK_FILE);
24284
+ const filePath = join31(repoRoot, OA_DIR, "history", PENDING_TASK_FILE);
23335
24285
  try {
23336
- if (!existsSync21(filePath))
24286
+ if (!existsSync22(filePath))
23337
24287
  return null;
23338
- const data = JSON.parse(readFileSync15(filePath, "utf-8"));
24288
+ const data = JSON.parse(readFileSync16(filePath, "utf-8"));
23339
24289
  try {
23340
24290
  unlinkSync3(filePath);
23341
24291
  } catch {
@@ -23346,13 +24296,13 @@ function loadPendingTask(repoRoot) {
23346
24296
  }
23347
24297
  }
23348
24298
  function saveSessionContext(repoRoot, entry) {
23349
- const contextDir = join30(repoRoot, OA_DIR, "context");
23350
- mkdirSync7(contextDir, { recursive: true });
23351
- const filePath = join30(contextDir, CONTEXT_SAVE_FILE);
24299
+ const contextDir = join31(repoRoot, OA_DIR, "context");
24300
+ mkdirSync8(contextDir, { recursive: true });
24301
+ const filePath = join31(contextDir, CONTEXT_SAVE_FILE);
23352
24302
  let ctx;
23353
24303
  try {
23354
- if (existsSync21(filePath)) {
23355
- ctx = JSON.parse(readFileSync15(filePath, "utf-8"));
24304
+ if (existsSync22(filePath)) {
24305
+ ctx = JSON.parse(readFileSync16(filePath, "utf-8"));
23356
24306
  } else {
23357
24307
  ctx = { entries: [], maxEntries: MAX_CONTEXT_ENTRIES, updatedAt: "" };
23358
24308
  }
@@ -23364,14 +24314,14 @@ function saveSessionContext(repoRoot, entry) {
23364
24314
  ctx.entries = ctx.entries.slice(-ctx.maxEntries);
23365
24315
  }
23366
24316
  ctx.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
23367
- writeFileSync7(filePath, JSON.stringify(ctx, null, 2) + "\n", "utf-8");
24317
+ writeFileSync8(filePath, JSON.stringify(ctx, null, 2) + "\n", "utf-8");
23368
24318
  }
23369
24319
  function loadSessionContext(repoRoot) {
23370
- const filePath = join30(repoRoot, OA_DIR, "context", CONTEXT_SAVE_FILE);
24320
+ const filePath = join31(repoRoot, OA_DIR, "context", CONTEXT_SAVE_FILE);
23371
24321
  try {
23372
- if (!existsSync21(filePath))
24322
+ if (!existsSync22(filePath))
23373
24323
  return null;
23374
- return JSON.parse(readFileSync15(filePath, "utf-8"));
24324
+ return JSON.parse(readFileSync16(filePath, "utf-8"));
23375
24325
  } catch {
23376
24326
  return null;
23377
24327
  }
@@ -23418,12 +24368,12 @@ function detectManifests(repoRoot) {
23418
24368
  { file: "docker-compose.yaml", type: "Docker Compose" }
23419
24369
  ];
23420
24370
  for (const check of checks) {
23421
- const filePath = join30(repoRoot, check.file);
23422
- if (existsSync21(filePath)) {
24371
+ const filePath = join31(repoRoot, check.file);
24372
+ if (existsSync22(filePath)) {
23423
24373
  let name;
23424
24374
  if (check.nameField) {
23425
24375
  try {
23426
- const data = JSON.parse(readFileSync15(filePath, "utf-8"));
24376
+ const data = JSON.parse(readFileSync16(filePath, "utf-8"));
23427
24377
  name = data[check.nameField];
23428
24378
  } catch {
23429
24379
  }
@@ -23452,7 +24402,7 @@ function findKeyFiles(repoRoot) {
23452
24402
  { pattern: "CLAUDE.md", description: "Claude Code context" }
23453
24403
  ];
23454
24404
  for (const check of checks) {
23455
- if (existsSync21(join30(repoRoot, check.pattern))) {
24405
+ if (existsSync22(join31(repoRoot, check.pattern))) {
23456
24406
  keyFiles.push({ path: check.pattern, description: check.description });
23457
24407
  }
23458
24408
  }
@@ -23478,12 +24428,12 @@ function buildDirTree(root, maxDepth, prefix = "", depth = 0) {
23478
24428
  if (entry.isDirectory()) {
23479
24429
  let fileCount = 0;
23480
24430
  try {
23481
- fileCount = readdirSync7(join30(root, entry.name)).filter((f) => !f.startsWith(".")).length;
24431
+ fileCount = readdirSync7(join31(root, entry.name)).filter((f) => !f.startsWith(".")).length;
23482
24432
  } catch {
23483
24433
  }
23484
24434
  result += `${prefix}${connector}${entry.name}/ (${fileCount})
23485
24435
  `;
23486
- result += buildDirTree(join30(root, entry.name), maxDepth, childPrefix, depth + 1);
24436
+ result += buildDirTree(join31(root, entry.name), maxDepth, childPrefix, depth + 1);
23487
24437
  } else if (depth < maxDepth) {
23488
24438
  result += `${prefix}${connector}${entry.name}
23489
24439
  `;
@@ -23537,8 +24487,8 @@ var init_oa_directory = __esm({
23537
24487
  // packages/cli/dist/tui/setup.js
23538
24488
  import * as readline from "node:readline";
23539
24489
  import { execSync as execSync20, spawn as spawn12 } from "node:child_process";
23540
- import { existsSync as existsSync22, writeFileSync as writeFileSync8, mkdirSync as mkdirSync8 } from "node:fs";
23541
- import { join as join31 } from "node:path";
24490
+ import { existsSync as existsSync23, writeFileSync as writeFileSync9, mkdirSync as mkdirSync9 } from "node:fs";
24491
+ import { join as join32 } from "node:path";
23542
24492
  import { homedir as homedir10, platform } from "node:os";
23543
24493
  function detectSystemSpecs() {
23544
24494
  let totalRamGB = 0;
@@ -23733,7 +24683,7 @@ async function installOllamaMac(rl) {
23733
24683
  execSync20('/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"', { stdio: "inherit", timeout: 6e5 });
23734
24684
  if (!hasCmd("brew")) {
23735
24685
  try {
23736
- const brewPrefix = existsSync22("/opt/homebrew/bin/brew") ? "/opt/homebrew" : "/usr/local";
24686
+ const brewPrefix = existsSync23("/opt/homebrew/bin/brew") ? "/opt/homebrew" : "/usr/local";
23737
24687
  process.env["PATH"] = `${brewPrefix}/bin:${process.env["PATH"]}`;
23738
24688
  } catch {
23739
24689
  }
@@ -24207,10 +25157,10 @@ async function doSetup(config, rl) {
24207
25157
  `PARAMETER num_predict ${numPredict}`,
24208
25158
  `PARAMETER stop "<|endoftext|>"`
24209
25159
  ].join("\n");
24210
- const modelDir2 = join31(homedir10(), ".open-agents", "models");
24211
- mkdirSync8(modelDir2, { recursive: true });
24212
- const modelfilePath = join31(modelDir2, `Modelfile.${customName}`);
24213
- writeFileSync8(modelfilePath, modelfileContent + "\n", "utf8");
25160
+ const modelDir2 = join32(homedir10(), ".open-agents", "models");
25161
+ mkdirSync9(modelDir2, { recursive: true });
25162
+ const modelfilePath = join32(modelDir2, `Modelfile.${customName}`);
25163
+ writeFileSync9(modelfilePath, modelfileContent + "\n", "utf8");
24214
25164
  process.stdout.write(` ${c2.dim("Creating model...")} `);
24215
25165
  execSync20(`ollama create ${customName} -f ${modelfilePath}`, {
24216
25166
  stdio: "pipe",
@@ -24255,7 +25205,7 @@ async function isModelAvailable(config) {
24255
25205
  }
24256
25206
  function isFirstRun() {
24257
25207
  try {
24258
- return !existsSync22(join31(homedir10(), ".open-agents", "config.json"));
25208
+ return !existsSync23(join32(homedir10(), ".open-agents", "config.json"));
24259
25209
  } catch {
24260
25210
  return true;
24261
25211
  }
@@ -24292,7 +25242,7 @@ function detectPkgManager() {
24292
25242
  return null;
24293
25243
  }
24294
25244
  function getVenvDir() {
24295
- return join31(homedir10(), ".open-agents", "venv");
25245
+ return join32(homedir10(), ".open-agents", "venv");
24296
25246
  }
24297
25247
  function hasVenvModule() {
24298
25248
  try {
@@ -24304,8 +25254,8 @@ function hasVenvModule() {
24304
25254
  }
24305
25255
  function ensureVenv(log) {
24306
25256
  const venvDir = getVenvDir();
24307
- const venvPip = join31(venvDir, "bin", "pip");
24308
- if (existsSync22(venvPip))
25257
+ const venvPip = join32(venvDir, "bin", "pip");
25258
+ if (existsSync23(venvPip))
24309
25259
  return venvDir;
24310
25260
  log("Creating Python venv for vision deps...");
24311
25261
  if (!hasCmd("python3")) {
@@ -24317,9 +25267,9 @@ function ensureVenv(log) {
24317
25267
  return null;
24318
25268
  }
24319
25269
  try {
24320
- mkdirSync8(join31(homedir10(), ".open-agents"), { recursive: true });
25270
+ mkdirSync9(join32(homedir10(), ".open-agents"), { recursive: true });
24321
25271
  execSync20(`python3 -m venv "${venvDir}"`, { stdio: "pipe", timeout: 3e4 });
24322
- execSync20(`"${join31(venvDir, "bin", "pip")}" install --upgrade pip`, {
25272
+ execSync20(`"${join32(venvDir, "bin", "pip")}" install --upgrade pip`, {
24323
25273
  stdio: "pipe",
24324
25274
  timeout: 6e4
24325
25275
  });
@@ -24528,15 +25478,15 @@ async function ensureVisionDeps(onInfo, getSudoPassword) {
24528
25478
  }
24529
25479
  }
24530
25480
  const venvDir = getVenvDir();
24531
- const venvBin = join31(venvDir, "bin");
24532
- const venvMoondream = join31(venvBin, "moondream-station");
25481
+ const venvBin = join32(venvDir, "bin");
25482
+ const venvMoondream = join32(venvBin, "moondream-station");
24533
25483
  const venv = ensureVenv(log);
24534
- if (venv && !hasCmd("moondream-station") && !existsSync22(venvMoondream)) {
24535
- const venvPip = join31(venvBin, "pip");
25484
+ if (venv && !hasCmd("moondream-station") && !existsSync23(venvMoondream)) {
25485
+ const venvPip = join32(venvBin, "pip");
24536
25486
  log("Installing moondream-station in ~/.open-agents/venv...");
24537
25487
  try {
24538
25488
  execSync20(`"${venvPip}" install moondream-station`, { stdio: "pipe", timeout: 3e5 });
24539
- if (existsSync22(venvMoondream)) {
25489
+ if (existsSync23(venvMoondream)) {
24540
25490
  log("moondream-station installed successfully.");
24541
25491
  } else {
24542
25492
  try {
@@ -24553,8 +25503,8 @@ async function ensureVisionDeps(onInfo, getSudoPassword) {
24553
25503
  }
24554
25504
  }
24555
25505
  if (venv) {
24556
- const venvPython = join31(venvBin, "python");
24557
- const venvPip2 = join31(venvBin, "pip");
25506
+ const venvPython = join32(venvBin, "python");
25507
+ const venvPip2 = join32(venvBin, "pip");
24558
25508
  let ocrStackInstalled = false;
24559
25509
  try {
24560
25510
  execSync20(`"${venvPython}" -c "import cv2, pytesseract, numpy, PIL"`, { stdio: "pipe", timeout: 1e4 });
@@ -24663,10 +25613,10 @@ function createExpandedVariant(baseModel, specs, sizeGB) {
24663
25613
  `PARAMETER num_predict ${numPredict}`,
24664
25614
  `PARAMETER stop "<|endoftext|>"`
24665
25615
  ].join("\n");
24666
- const modelDir2 = join31(homedir10(), ".open-agents", "models");
24667
- mkdirSync8(modelDir2, { recursive: true });
24668
- const modelfilePath = join31(modelDir2, `Modelfile.${customName}`);
24669
- writeFileSync8(modelfilePath, modelfileContent + "\n", "utf8");
25616
+ const modelDir2 = join32(homedir10(), ".open-agents", "models");
25617
+ mkdirSync9(modelDir2, { recursive: true });
25618
+ const modelfilePath = join32(modelDir2, `Modelfile.${customName}`);
25619
+ writeFileSync9(modelfilePath, modelfileContent + "\n", "utf8");
24670
25620
  execSync20(`ollama create ${customName} -f ${modelfilePath}`, {
24671
25621
  stdio: "pipe",
24672
25622
  timeout: 12e4
@@ -25229,6 +26179,130 @@ async function handleSlashCommand(input, ctx) {
25229
26179
  }
25230
26180
  return "handled";
25231
26181
  }
26182
+ case "p2p": {
26183
+ if (!ctx.p2pStart) {
26184
+ renderWarning("P2P mesh not available in this context.");
26185
+ return "handled";
26186
+ }
26187
+ const p2pSub = arg?.split(/\s+/)[0] ?? "";
26188
+ const p2pRest = arg?.slice(p2pSub.length).trim() ?? "";
26189
+ if (p2pSub === "stop" || p2pSub === "off") {
26190
+ if (ctx.isP2PActive?.()) {
26191
+ await ctx.p2pStop?.();
26192
+ renderInfo("P2P mesh stopped.");
26193
+ } else {
26194
+ renderWarning("P2P mesh not running.");
26195
+ }
26196
+ return "handled";
26197
+ }
26198
+ if (p2pSub === "status" || !p2pSub && ctx.isP2PActive?.()) {
26199
+ if (ctx.isP2PActive?.()) {
26200
+ process.stdout.write("\n" + (ctx.p2pStatus?.() ?? "") + "\n\n");
26201
+ } else {
26202
+ renderWarning("P2P mesh not running. Start with: /p2p start");
26203
+ }
26204
+ return "handled";
26205
+ }
26206
+ if (p2pSub === "connect") {
26207
+ if (!ctx.isP2PActive?.()) {
26208
+ renderWarning("Start mesh first: /p2p start");
26209
+ return "handled";
26210
+ }
26211
+ if (!p2pRest) {
26212
+ renderInfo("Usage: /p2p connect <endpoint> [--trust verified|tee|public]");
26213
+ return "handled";
26214
+ }
26215
+ const parts = p2pRest.split(/\s+/);
26216
+ const endpoint = parts[0];
26217
+ const trustIdx = parts.indexOf("--trust");
26218
+ const trust = trustIdx !== -1 ? parts[trustIdx + 1] : void 0;
26219
+ try {
26220
+ const peerId = await ctx.p2pConnect(endpoint, trust);
26221
+ if (peerId) {
26222
+ renderInfo(`Connected to peer: ${c2.bold(peerId)}`);
26223
+ } else {
26224
+ renderError("Failed to connect to peer.");
26225
+ }
26226
+ } catch (err) {
26227
+ renderError(`Connection failed: ${err instanceof Error ? err.message : String(err)}`);
26228
+ }
26229
+ return "handled";
26230
+ }
26231
+ if (p2pSub === "start" || !p2pSub) {
26232
+ if (ctx.isP2PActive?.()) {
26233
+ process.stdout.write("\n" + (ctx.p2pStatus?.() ?? "") + "\n\n");
26234
+ return "handled";
26235
+ }
26236
+ try {
26237
+ const bootstrapPeers = p2pRest ? p2pRest.split(/[,\s]+/).filter(Boolean) : void 0;
26238
+ const result = await ctx.p2pStart(bootstrapPeers);
26239
+ renderInfo(`P2P mesh started. Peer ID: ${c2.bold(result.peerId)} (port ${result.port})`);
26240
+ renderInfo("Connect peers with: /p2p connect <endpoint>");
26241
+ } catch (err) {
26242
+ renderError(`P2P start failed: ${err instanceof Error ? err.message : String(err)}`);
26243
+ }
26244
+ return "handled";
26245
+ }
26246
+ renderInfo("Usage: /p2p start [bootstrap-peers...] | /p2p connect <endpoint> | /p2p status | /p2p stop");
26247
+ return "handled";
26248
+ }
26249
+ case "secrets":
26250
+ case "secret":
26251
+ case "vault": {
26252
+ if (!ctx.secretSet) {
26253
+ renderWarning("Secret vault not available in this context.");
26254
+ return "handled";
26255
+ }
26256
+ const secSub = arg?.split(/\s+/)[0] ?? "";
26257
+ const secRest = arg?.slice(secSub.length).trim() ?? "";
26258
+ if (secSub === "set") {
26259
+ const parts = secRest.split(/\s+/);
26260
+ const name = parts[0];
26261
+ const value = parts.slice(1).join(" ");
26262
+ if (!name || !value) {
26263
+ renderInfo("Usage: /secrets set <NAME> <value>");
26264
+ return "handled";
26265
+ }
26266
+ ctx.secretSet(name, value);
26267
+ renderInfo(`Secret "${name}" registered.`);
26268
+ return "handled";
26269
+ }
26270
+ if (secSub === "delete" || secSub === "rm") {
26271
+ if (!secRest) {
26272
+ renderInfo("Usage: /secrets delete <NAME>");
26273
+ return "handled";
26274
+ }
26275
+ const deleted = ctx.secretDelete(secRest);
26276
+ if (deleted)
26277
+ renderInfo(`Secret "${secRest}" removed.`);
26278
+ else
26279
+ renderWarning(`Secret "${secRest}" not found.`);
26280
+ return "handled";
26281
+ }
26282
+ if (secSub === "import-env") {
26283
+ const count = ctx.secretImportEnv();
26284
+ renderInfo(`Imported ${count} secrets from environment variables.`);
26285
+ return "handled";
26286
+ }
26287
+ const secrets = ctx.secretList();
26288
+ if (secrets.length === 0) {
26289
+ renderInfo("No secrets registered. Use: /secrets set <NAME> <value>");
26290
+ } else {
26291
+ process.stdout.write(`
26292
+ ${c2.bold("Registered Secrets")} (${secrets.length})
26293
+
26294
+ `);
26295
+ for (const s of secrets) {
26296
+ process.stdout.write(` ${c2.cyan(s.name.padEnd(24))} min trust: ${c2.bold(s.minTrust)}
26297
+ `);
26298
+ }
26299
+ process.stdout.write(`
26300
+ ${c2.dim("Secrets are redacted from prompts sent to peers below their trust tier.")}
26301
+
26302
+ `);
26303
+ }
26304
+ return "handled";
26305
+ }
25232
26306
  case "bruteforce":
25233
26307
  case "brute": {
25234
26308
  const isOn = ctx.bruteForceToggle();
@@ -25679,17 +26753,17 @@ async function handleUpdate(subcommand, ctx) {
25679
26753
  try {
25680
26754
  const { createRequire: createRequire4 } = await import("node:module");
25681
26755
  const { fileURLToPath: fileURLToPath11 } = await import("node:url");
25682
- const { dirname: dirname14, join: join45 } = await import("node:path");
25683
- const { existsSync: existsSync32 } = await import("node:fs");
26756
+ const { dirname: dirname15, join: join46 } = await import("node:path");
26757
+ const { existsSync: existsSync33 } = await import("node:fs");
25684
26758
  const req = createRequire4(import.meta.url);
25685
- const thisDir = dirname14(fileURLToPath11(import.meta.url));
26759
+ const thisDir = dirname15(fileURLToPath11(import.meta.url));
25686
26760
  const candidates = [
25687
- join45(thisDir, "..", "package.json"),
25688
- join45(thisDir, "..", "..", "package.json"),
25689
- join45(thisDir, "..", "..", "..", "package.json")
26761
+ join46(thisDir, "..", "package.json"),
26762
+ join46(thisDir, "..", "..", "package.json"),
26763
+ join46(thisDir, "..", "..", "..", "package.json")
25690
26764
  ];
25691
26765
  for (const pkgPath of candidates) {
25692
- if (existsSync32(pkgPath)) {
26766
+ if (existsSync33(pkgPath)) {
25693
26767
  const pkg = req(pkgPath);
25694
26768
  if (pkg.name === "open-agents-ai" || pkg.name === "@open-agents/cli") {
25695
26769
  currentVersion = pkg.version ?? "0.0.0";
@@ -25892,8 +26966,8 @@ var init_commands = __esm({
25892
26966
  });
25893
26967
 
25894
26968
  // packages/cli/dist/tui/project-context.js
25895
- import { existsSync as existsSync23, readFileSync as readFileSync16, readdirSync as readdirSync8 } from "node:fs";
25896
- import { join as join32, basename as basename10 } from "node:path";
26969
+ import { existsSync as existsSync24, readFileSync as readFileSync17, readdirSync as readdirSync8 } from "node:fs";
26970
+ import { join as join33, basename as basename10 } from "node:path";
25897
26971
  import { execSync as execSync21 } from "node:child_process";
25898
26972
  import { homedir as homedir11, platform as platform2, release } from "node:os";
25899
26973
  function getModelTier(modelName) {
@@ -25928,10 +27002,10 @@ function loadProjectMap(repoRoot) {
25928
27002
  if (!hasOaDirectory(repoRoot)) {
25929
27003
  initOaDirectory(repoRoot);
25930
27004
  }
25931
- const mapPath = join32(repoRoot, OA_DIR, "context", "project-map.md");
25932
- if (existsSync23(mapPath)) {
27005
+ const mapPath = join33(repoRoot, OA_DIR, "context", "project-map.md");
27006
+ if (existsSync24(mapPath)) {
25933
27007
  try {
25934
- const content = readFileSync16(mapPath, "utf-8");
27008
+ const content = readFileSync17(mapPath, "utf-8");
25935
27009
  return content;
25936
27010
  } catch {
25937
27011
  }
@@ -25972,31 +27046,31 @@ ${log}`);
25972
27046
  }
25973
27047
  function loadMemoryContext(repoRoot) {
25974
27048
  const sections = [];
25975
- const oaMemDir = join32(repoRoot, OA_DIR, "memory");
27049
+ const oaMemDir = join33(repoRoot, OA_DIR, "memory");
25976
27050
  const oaEntries = loadMemoryDir(oaMemDir, "project");
25977
27051
  if (oaEntries)
25978
27052
  sections.push(oaEntries);
25979
- const legacyMemDir = join32(repoRoot, ".open-agents", "memory");
25980
- if (legacyMemDir !== oaMemDir && existsSync23(legacyMemDir)) {
27053
+ const legacyMemDir = join33(repoRoot, ".open-agents", "memory");
27054
+ if (legacyMemDir !== oaMemDir && existsSync24(legacyMemDir)) {
25981
27055
  const legacyEntries = loadMemoryDir(legacyMemDir, "project/legacy");
25982
27056
  if (legacyEntries)
25983
27057
  sections.push(legacyEntries);
25984
27058
  }
25985
- const globalMemDir = join32(homedir11(), ".open-agents", "memory");
27059
+ const globalMemDir = join33(homedir11(), ".open-agents", "memory");
25986
27060
  const globalEntries = loadMemoryDir(globalMemDir, "global");
25987
27061
  if (globalEntries)
25988
27062
  sections.push(globalEntries);
25989
27063
  return sections.join("\n\n");
25990
27064
  }
25991
27065
  function loadMemoryDir(memDir, scope) {
25992
- if (!existsSync23(memDir))
27066
+ if (!existsSync24(memDir))
25993
27067
  return "";
25994
27068
  const lines = [];
25995
27069
  try {
25996
27070
  const files = readdirSync8(memDir).filter((f) => f.endsWith(".json"));
25997
27071
  for (const file of files.slice(0, 10)) {
25998
27072
  try {
25999
- const raw = readFileSync16(join32(memDir, file), "utf-8");
27073
+ const raw = readFileSync17(join33(memDir, file), "utf-8");
26000
27074
  const entries = JSON.parse(raw);
26001
27075
  const topic = basename10(file, ".json");
26002
27076
  const keys = Object.keys(entries);
@@ -27023,22 +28097,22 @@ var init_carousel = __esm({
27023
28097
  });
27024
28098
 
27025
28099
  // packages/cli/dist/tui/carousel-descriptors.js
27026
- import { existsSync as existsSync24, readFileSync as readFileSync17, writeFileSync as writeFileSync9, mkdirSync as mkdirSync9, readdirSync as readdirSync9 } from "node:fs";
27027
- import { join as join33, basename as basename11 } from "node:path";
28100
+ import { existsSync as existsSync25, readFileSync as readFileSync18, writeFileSync as writeFileSync10, mkdirSync as mkdirSync10, readdirSync as readdirSync9 } from "node:fs";
28101
+ import { join as join34, basename as basename11 } from "node:path";
27028
28102
  function loadToolProfile(repoRoot) {
27029
- const filePath = join33(repoRoot, OA_DIR, "context", TOOL_PROFILE_FILE);
28103
+ const filePath = join34(repoRoot, OA_DIR, "context", TOOL_PROFILE_FILE);
27030
28104
  try {
27031
- if (!existsSync24(filePath))
28105
+ if (!existsSync25(filePath))
27032
28106
  return null;
27033
- return JSON.parse(readFileSync17(filePath, "utf-8"));
28107
+ return JSON.parse(readFileSync18(filePath, "utf-8"));
27034
28108
  } catch {
27035
28109
  return null;
27036
28110
  }
27037
28111
  }
27038
28112
  function saveToolProfile(repoRoot, profile) {
27039
- const contextDir = join33(repoRoot, OA_DIR, "context");
27040
- mkdirSync9(contextDir, { recursive: true });
27041
- writeFileSync9(join33(contextDir, TOOL_PROFILE_FILE), JSON.stringify(profile, null, 2), "utf-8");
28113
+ const contextDir = join34(repoRoot, OA_DIR, "context");
28114
+ mkdirSync10(contextDir, { recursive: true });
28115
+ writeFileSync10(join34(contextDir, TOOL_PROFILE_FILE), JSON.stringify(profile, null, 2), "utf-8");
27042
28116
  }
27043
28117
  function categorizeToolCall(toolName) {
27044
28118
  for (const cat of TOOL_CATEGORIES) {
@@ -27096,25 +28170,25 @@ function weightedColor(profile) {
27096
28170
  return selectedCat.colors[Math.floor(Math.random() * selectedCat.colors.length)];
27097
28171
  }
27098
28172
  function loadCachedDescriptors(repoRoot) {
27099
- const filePath = join33(repoRoot, OA_DIR, "context", DESCRIPTOR_FILE);
28173
+ const filePath = join34(repoRoot, OA_DIR, "context", DESCRIPTOR_FILE);
27100
28174
  try {
27101
- if (!existsSync24(filePath))
28175
+ if (!existsSync25(filePath))
27102
28176
  return null;
27103
- const cached = JSON.parse(readFileSync17(filePath, "utf-8"));
28177
+ const cached = JSON.parse(readFileSync18(filePath, "utf-8"));
27104
28178
  return cached.phrases.length > 0 ? cached.phrases : null;
27105
28179
  } catch {
27106
28180
  return null;
27107
28181
  }
27108
28182
  }
27109
28183
  function saveCachedDescriptors(repoRoot, phrases, sourceHash) {
27110
- const contextDir = join33(repoRoot, OA_DIR, "context");
27111
- mkdirSync9(contextDir, { recursive: true });
28184
+ const contextDir = join34(repoRoot, OA_DIR, "context");
28185
+ mkdirSync10(contextDir, { recursive: true });
27112
28186
  const cached = {
27113
28187
  phrases,
27114
28188
  generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
27115
28189
  sourceHash
27116
28190
  };
27117
- writeFileSync9(join33(contextDir, DESCRIPTOR_FILE), JSON.stringify(cached, null, 2), "utf-8");
28191
+ writeFileSync10(join34(contextDir, DESCRIPTOR_FILE), JSON.stringify(cached, null, 2), "utf-8");
27118
28192
  }
27119
28193
  function generateDescriptors(repoRoot) {
27120
28194
  const profile = loadToolProfile(repoRoot);
@@ -27162,11 +28236,11 @@ function generateDescriptors(repoRoot) {
27162
28236
  return phrases;
27163
28237
  }
27164
28238
  function extractFromPackageJson(repoRoot, tags) {
27165
- const pkgPath = join33(repoRoot, "package.json");
28239
+ const pkgPath = join34(repoRoot, "package.json");
27166
28240
  try {
27167
- if (!existsSync24(pkgPath))
28241
+ if (!existsSync25(pkgPath))
27168
28242
  return;
27169
- const pkg = JSON.parse(readFileSync17(pkgPath, "utf-8"));
28243
+ const pkg = JSON.parse(readFileSync18(pkgPath, "utf-8"));
27170
28244
  if (pkg.name && typeof pkg.name === "string") {
27171
28245
  const parts = pkg.name.replace(/^@/, "").split("/");
27172
28246
  for (const p of parts)
@@ -27210,7 +28284,7 @@ function extractFromManifests(repoRoot, tags) {
27210
28284
  { file: ".github/workflows", tag: "ci/cd" }
27211
28285
  ];
27212
28286
  for (const check of manifestChecks) {
27213
- if (existsSync24(join33(repoRoot, check.file))) {
28287
+ if (existsSync25(join34(repoRoot, check.file))) {
27214
28288
  tags.push(check.tag);
27215
28289
  }
27216
28290
  }
@@ -27232,16 +28306,16 @@ function extractFromSessions(repoRoot, tags) {
27232
28306
  }
27233
28307
  }
27234
28308
  function extractFromMemory(repoRoot, tags) {
27235
- const memoryDir = join33(repoRoot, OA_DIR, "memory");
28309
+ const memoryDir = join34(repoRoot, OA_DIR, "memory");
27236
28310
  try {
27237
- if (!existsSync24(memoryDir))
28311
+ if (!existsSync25(memoryDir))
27238
28312
  return;
27239
28313
  const files = readdirSync9(memoryDir).filter((f) => f.endsWith(".json"));
27240
28314
  for (const file of files) {
27241
28315
  const topic = file.replace(/\.json$/, "").replace(/[-_]/g, " ");
27242
28316
  tags.push(topic);
27243
28317
  try {
27244
- const data = JSON.parse(readFileSync17(join33(memoryDir, file), "utf-8"));
28318
+ const data = JSON.parse(readFileSync18(join34(memoryDir, file), "utf-8"));
27245
28319
  if (data && typeof data === "object") {
27246
28320
  const keys = Object.keys(data).slice(0, 3);
27247
28321
  for (const key of keys) {
@@ -27376,25 +28450,25 @@ var init_carousel_descriptors = __esm({
27376
28450
  });
27377
28451
 
27378
28452
  // packages/cli/dist/tui/voice.js
27379
- import { existsSync as existsSync25, mkdirSync as mkdirSync10, writeFileSync as writeFileSync10, readFileSync as readFileSync18, unlinkSync as unlinkSync4 } from "node:fs";
27380
- import { join as join34 } from "node:path";
28453
+ import { existsSync as existsSync26, mkdirSync as mkdirSync11, writeFileSync as writeFileSync11, readFileSync as readFileSync19, unlinkSync as unlinkSync4 } from "node:fs";
28454
+ import { join as join35 } from "node:path";
27381
28455
  import { homedir as homedir12, tmpdir as tmpdir6, platform as platform3 } from "node:os";
27382
28456
  import { execSync as execSync22, spawn as nodeSpawn } from "node:child_process";
27383
28457
  import { createRequire } from "node:module";
27384
28458
  function voiceDir() {
27385
- return join34(homedir12(), ".open-agents", "voice");
28459
+ return join35(homedir12(), ".open-agents", "voice");
27386
28460
  }
27387
28461
  function modelsDir() {
27388
- return join34(voiceDir(), "models");
28462
+ return join35(voiceDir(), "models");
27389
28463
  }
27390
28464
  function modelDir(id) {
27391
- return join34(modelsDir(), id);
28465
+ return join35(modelsDir(), id);
27392
28466
  }
27393
28467
  function modelOnnxPath(id) {
27394
- return join34(modelDir(id), "model.onnx");
28468
+ return join35(modelDir(id), "model.onnx");
27395
28469
  }
27396
28470
  function modelConfigPath(id) {
27397
- return join34(modelDir(id), "config.json");
28471
+ return join35(modelDir(id), "config.json");
27398
28472
  }
27399
28473
  function resetNarrationContext() {
27400
28474
  narration.toolCount = 0;
@@ -28072,7 +29146,7 @@ var init_voice = __esm({
28072
29146
  }
28073
29147
  this.onPCMOutput(Buffer.from(int16.buffer), this.config.audio.sample_rate);
28074
29148
  }
28075
- const wavPath = join34(tmpdir6(), `oa-voice-${Date.now()}.wav`);
29149
+ const wavPath = join35(tmpdir6(), `oa-voice-${Date.now()}.wav`);
28076
29150
  this.writeWav(audioData, this.config.audio.sample_rate, wavPath);
28077
29151
  await this.playWav(wavPath);
28078
29152
  try {
@@ -28156,7 +29230,7 @@ var init_voice = __esm({
28156
29230
  return buffer;
28157
29231
  }
28158
29232
  writeWav(samples, sampleRate, path) {
28159
- writeFileSync10(path, this.buildWavBuffer(samples, sampleRate));
29233
+ writeFileSync11(path, this.buildWavBuffer(samples, sampleRate));
28160
29234
  }
28161
29235
  // -------------------------------------------------------------------------
28162
29236
  // Audio playback (system default speakers)
@@ -28230,30 +29304,30 @@ var init_voice = __esm({
28230
29304
  return;
28231
29305
  const arch = process.arch;
28232
29306
  const isArmLinux = (arch === "arm64" || arch === "arm") && process.platform === "linux";
28233
- mkdirSync10(voiceDir(), { recursive: true });
28234
- const pkgPath = join34(voiceDir(), "package.json");
29307
+ mkdirSync11(voiceDir(), { recursive: true });
29308
+ const pkgPath = join35(voiceDir(), "package.json");
28235
29309
  const expectedDeps = {
28236
29310
  "onnxruntime-node": "^1.21.0",
28237
29311
  "phonemizer": "^1.2.1"
28238
29312
  };
28239
- if (existsSync25(pkgPath)) {
29313
+ if (existsSync26(pkgPath)) {
28240
29314
  try {
28241
- const existing = JSON.parse(readFileSync18(pkgPath, "utf8"));
29315
+ const existing = JSON.parse(readFileSync19(pkgPath, "utf8"));
28242
29316
  if (!existing.dependencies?.["phonemizer"]) {
28243
29317
  existing.dependencies = { ...existing.dependencies, ...expectedDeps };
28244
- writeFileSync10(pkgPath, JSON.stringify(existing, null, 2));
29318
+ writeFileSync11(pkgPath, JSON.stringify(existing, null, 2));
28245
29319
  }
28246
29320
  } catch {
28247
29321
  }
28248
29322
  }
28249
- if (!existsSync25(pkgPath)) {
28250
- writeFileSync10(pkgPath, JSON.stringify({
29323
+ if (!existsSync26(pkgPath)) {
29324
+ writeFileSync11(pkgPath, JSON.stringify({
28251
29325
  name: "open-agents-voice",
28252
29326
  private: true,
28253
29327
  dependencies: expectedDeps
28254
29328
  }, null, 2));
28255
29329
  }
28256
- const voiceRequire = createRequire(join34(voiceDir(), "index.js"));
29330
+ const voiceRequire = createRequire(join35(voiceDir(), "index.js"));
28257
29331
  try {
28258
29332
  this.ort = voiceRequire("onnxruntime-node");
28259
29333
  } catch {
@@ -28307,18 +29381,18 @@ Error: ${err instanceof Error ? err.message : String(err)}`);
28307
29381
  const dir = modelDir(id);
28308
29382
  const onnxPath = modelOnnxPath(id);
28309
29383
  const configPath = modelConfigPath(id);
28310
- if (existsSync25(onnxPath) && existsSync25(configPath))
29384
+ if (existsSync26(onnxPath) && existsSync26(configPath))
28311
29385
  return;
28312
- mkdirSync10(dir, { recursive: true });
28313
- if (!existsSync25(configPath)) {
29386
+ mkdirSync11(dir, { recursive: true });
29387
+ if (!existsSync26(configPath)) {
28314
29388
  renderInfo(`Downloading ${model.label} voice config...`);
28315
29389
  const configResp = await fetch(model.configUrl);
28316
29390
  if (!configResp.ok)
28317
29391
  throw new Error(`Failed to download config: HTTP ${configResp.status}`);
28318
29392
  const configText = await configResp.text();
28319
- writeFileSync10(configPath, configText);
29393
+ writeFileSync11(configPath, configText);
28320
29394
  }
28321
- if (!existsSync25(onnxPath)) {
29395
+ if (!existsSync26(onnxPath)) {
28322
29396
  renderInfo(`Downloading ${model.label} voice model (this may take a minute)...`);
28323
29397
  const onnxResp = await fetch(model.onnxUrl);
28324
29398
  if (!onnxResp.ok)
@@ -28342,7 +29416,7 @@ Error: ${err instanceof Error ? err.message : String(err)}`);
28342
29416
  }
28343
29417
  process.stdout.write("\r" + " ".repeat(60) + "\r");
28344
29418
  const fullBuffer = Buffer.concat(chunks);
28345
- writeFileSync10(onnxPath, fullBuffer);
29419
+ writeFileSync11(onnxPath, fullBuffer);
28346
29420
  renderInfo(`${model.label} model downloaded (${formatBytes2(fullBuffer.length)}).`);
28347
29421
  }
28348
29422
  }
@@ -28354,10 +29428,10 @@ Error: ${err instanceof Error ? err.message : String(err)}`);
28354
29428
  throw new Error("ONNX runtime not loaded");
28355
29429
  const onnxPath = modelOnnxPath(this.modelId);
28356
29430
  const configPath = modelConfigPath(this.modelId);
28357
- if (!existsSync25(onnxPath) || !existsSync25(configPath)) {
29431
+ if (!existsSync26(onnxPath) || !existsSync26(configPath)) {
28358
29432
  throw new Error(`Model files not found for ${this.modelId}`);
28359
29433
  }
28360
- this.config = JSON.parse(readFileSync18(configPath, "utf8"));
29434
+ this.config = JSON.parse(readFileSync19(configPath, "utf8"));
28361
29435
  renderInfo("Loading voice model...");
28362
29436
  this.session = await this.ort.InferenceSession.create(onnxPath, {
28363
29437
  executionProviders: ["cpu"],
@@ -29220,13 +30294,13 @@ var init_stream_renderer = __esm({
29220
30294
  });
29221
30295
 
29222
30296
  // packages/cli/dist/tui/edit-history.js
29223
- import { appendFileSync as appendFileSync2, mkdirSync as mkdirSync11 } from "node:fs";
29224
- import { join as join35 } from "node:path";
30297
+ import { appendFileSync as appendFileSync2, mkdirSync as mkdirSync12 } from "node:fs";
30298
+ import { join as join36 } from "node:path";
29225
30299
  function createEditHistoryLogger(repoRoot, sessionId) {
29226
- const historyDir = join35(repoRoot, ".oa", "history");
29227
- const logPath = join35(historyDir, "edits.jsonl");
30300
+ const historyDir = join36(repoRoot, ".oa", "history");
30301
+ const logPath = join36(historyDir, "edits.jsonl");
29228
30302
  try {
29229
- mkdirSync11(historyDir, { recursive: true });
30303
+ mkdirSync12(historyDir, { recursive: true });
29230
30304
  } catch {
29231
30305
  }
29232
30306
  function logToolCall(toolName, toolArgs, success) {
@@ -29335,15 +30409,15 @@ var init_edit_history = __esm({
29335
30409
  });
29336
30410
 
29337
30411
  // packages/cli/dist/tui/dream-engine.js
29338
- import { mkdirSync as mkdirSync12, writeFileSync as writeFileSync11, readFileSync as readFileSync19, existsSync as existsSync26, cpSync, rmSync, readdirSync as readdirSync10 } from "node:fs";
29339
- import { join as join36, basename as basename12 } from "node:path";
30412
+ import { mkdirSync as mkdirSync13, writeFileSync as writeFileSync12, readFileSync as readFileSync20, existsSync as existsSync27, cpSync, rmSync, readdirSync as readdirSync10 } from "node:fs";
30413
+ import { join as join37, basename as basename12 } from "node:path";
29340
30414
  import { execSync as execSync23 } from "node:child_process";
29341
30415
  function loadAutoresearchMemory(repoRoot) {
29342
- const memoryPath = join36(repoRoot, ".oa", "memory", "autoresearch.json");
29343
- if (!existsSync26(memoryPath))
30416
+ const memoryPath = join37(repoRoot, ".oa", "memory", "autoresearch.json");
30417
+ if (!existsSync27(memoryPath))
29344
30418
  return "";
29345
30419
  try {
29346
- const raw = readFileSync19(memoryPath, "utf-8");
30420
+ const raw = readFileSync20(memoryPath, "utf-8");
29347
30421
  const data = JSON.parse(raw);
29348
30422
  const sections = [];
29349
30423
  for (const key of AUTORESEARCH_MEMORY_KEYS) {
@@ -29563,14 +30637,14 @@ var init_dream_engine = __esm({
29563
30637
  const content = String(args["content"] ?? "");
29564
30638
  if (!rawPath)
29565
30639
  return { success: false, output: "", error: "path is required", durationMs: Date.now() - start };
29566
- const targetPath = rawPath.startsWith("/") || rawPath.startsWith(".oa/autoresearch") ? join36(this.autoresearchDir, basename12(rawPath)) : join36(this.autoresearchDir, rawPath);
30640
+ const targetPath = rawPath.startsWith("/") || rawPath.startsWith(".oa/autoresearch") ? join37(this.autoresearchDir, basename12(rawPath)) : join37(this.autoresearchDir, rawPath);
29567
30641
  if (!targetPath.startsWith(this.autoresearchDir)) {
29568
30642
  return { success: false, output: "", error: "Autoresearch mode: writes are confined to .oa/autoresearch/", durationMs: Date.now() - start };
29569
30643
  }
29570
30644
  try {
29571
- const dir = join36(targetPath, "..");
29572
- mkdirSync12(dir, { recursive: true });
29573
- writeFileSync11(targetPath, content, "utf-8");
30645
+ const dir = join37(targetPath, "..");
30646
+ mkdirSync13(dir, { recursive: true });
30647
+ writeFileSync12(targetPath, content, "utf-8");
29574
30648
  return { success: true, output: `Wrote ${content.length} bytes to ${rawPath}`, durationMs: Date.now() - start };
29575
30649
  } catch (err) {
29576
30650
  return { success: false, output: "", error: String(err), durationMs: Date.now() - start };
@@ -29598,20 +30672,20 @@ var init_dream_engine = __esm({
29598
30672
  const rawPath = String(args["path"] ?? "");
29599
30673
  const oldStr = String(args["old_string"] ?? "");
29600
30674
  const newStr = String(args["new_string"] ?? "");
29601
- const targetPath = rawPath.startsWith("/") || rawPath.startsWith(".oa/autoresearch") ? join36(this.autoresearchDir, basename12(rawPath)) : join36(this.autoresearchDir, rawPath);
30675
+ const targetPath = rawPath.startsWith("/") || rawPath.startsWith(".oa/autoresearch") ? join37(this.autoresearchDir, basename12(rawPath)) : join37(this.autoresearchDir, rawPath);
29602
30676
  if (!targetPath.startsWith(this.autoresearchDir)) {
29603
30677
  return { success: false, output: "", error: "Autoresearch mode: edits are confined to .oa/autoresearch/", durationMs: Date.now() - start };
29604
30678
  }
29605
30679
  try {
29606
- if (!existsSync26(targetPath)) {
30680
+ if (!existsSync27(targetPath)) {
29607
30681
  return { success: false, output: "", error: `File not found: ${rawPath}`, durationMs: Date.now() - start };
29608
30682
  }
29609
- let content = readFileSync19(targetPath, "utf-8");
30683
+ let content = readFileSync20(targetPath, "utf-8");
29610
30684
  if (!content.includes(oldStr)) {
29611
30685
  return { success: false, output: "", error: "old_string not found in file", durationMs: Date.now() - start };
29612
30686
  }
29613
30687
  content = content.replace(oldStr, newStr);
29614
- writeFileSync11(targetPath, content, "utf-8");
30688
+ writeFileSync12(targetPath, content, "utf-8");
29615
30689
  return { success: true, output: `Edited ${rawPath}`, durationMs: Date.now() - start };
29616
30690
  } catch (err) {
29617
30691
  return { success: false, output: "", error: String(err), durationMs: Date.now() - start };
@@ -29652,14 +30726,14 @@ var init_dream_engine = __esm({
29652
30726
  const content = String(args["content"] ?? "");
29653
30727
  if (!rawPath)
29654
30728
  return { success: false, output: "", error: "path is required", durationMs: Date.now() - start };
29655
- const targetPath = rawPath.startsWith("/") || rawPath.startsWith(".oa/dreams") ? join36(this.dreamsDir, basename12(rawPath)) : join36(this.dreamsDir, rawPath);
30729
+ const targetPath = rawPath.startsWith("/") || rawPath.startsWith(".oa/dreams") ? join37(this.dreamsDir, basename12(rawPath)) : join37(this.dreamsDir, rawPath);
29656
30730
  if (!targetPath.startsWith(this.dreamsDir)) {
29657
30731
  return { success: false, output: "", error: "Dream mode: writes are confined to .oa/dreams/", durationMs: Date.now() - start };
29658
30732
  }
29659
30733
  try {
29660
- const dir = join36(targetPath, "..");
29661
- mkdirSync12(dir, { recursive: true });
29662
- writeFileSync11(targetPath, content, "utf-8");
30734
+ const dir = join37(targetPath, "..");
30735
+ mkdirSync13(dir, { recursive: true });
30736
+ writeFileSync12(targetPath, content, "utf-8");
29663
30737
  return { success: true, output: `Wrote ${content.length} bytes to ${rawPath}`, durationMs: Date.now() - start };
29664
30738
  } catch (err) {
29665
30739
  return { success: false, output: "", error: String(err), durationMs: Date.now() - start };
@@ -29687,20 +30761,20 @@ var init_dream_engine = __esm({
29687
30761
  const rawPath = String(args["path"] ?? "");
29688
30762
  const oldStr = String(args["old_string"] ?? "");
29689
30763
  const newStr = String(args["new_string"] ?? "");
29690
- const targetPath = rawPath.startsWith("/") || rawPath.startsWith(".oa/dreams") ? join36(this.dreamsDir, basename12(rawPath)) : join36(this.dreamsDir, rawPath);
30764
+ const targetPath = rawPath.startsWith("/") || rawPath.startsWith(".oa/dreams") ? join37(this.dreamsDir, basename12(rawPath)) : join37(this.dreamsDir, rawPath);
29691
30765
  if (!targetPath.startsWith(this.dreamsDir)) {
29692
30766
  return { success: false, output: "", error: "Dream mode: edits are confined to .oa/dreams/", durationMs: Date.now() - start };
29693
30767
  }
29694
30768
  try {
29695
- if (!existsSync26(targetPath)) {
30769
+ if (!existsSync27(targetPath)) {
29696
30770
  return { success: false, output: "", error: `File not found: ${rawPath}`, durationMs: Date.now() - start };
29697
30771
  }
29698
- let content = readFileSync19(targetPath, "utf-8");
30772
+ let content = readFileSync20(targetPath, "utf-8");
29699
30773
  if (!content.includes(oldStr)) {
29700
30774
  return { success: false, output: "", error: "old_string not found in file", durationMs: Date.now() - start };
29701
30775
  }
29702
30776
  content = content.replace(oldStr, newStr);
29703
- writeFileSync11(targetPath, content, "utf-8");
30777
+ writeFileSync12(targetPath, content, "utf-8");
29704
30778
  return { success: true, output: `Edited ${rawPath}`, durationMs: Date.now() - start };
29705
30779
  } catch (err) {
29706
30780
  return { success: false, output: "", error: String(err), durationMs: Date.now() - start };
@@ -29754,7 +30828,7 @@ var init_dream_engine = __esm({
29754
30828
  constructor(config, repoRoot) {
29755
30829
  this.config = config;
29756
30830
  this.repoRoot = repoRoot;
29757
- this.dreamsDir = join36(repoRoot, ".oa", "dreams");
30831
+ this.dreamsDir = join37(repoRoot, ".oa", "dreams");
29758
30832
  this.state = {
29759
30833
  mode: "default",
29760
30834
  active: false,
@@ -29785,7 +30859,7 @@ var init_dream_engine = __esm({
29785
30859
  startedAt: (/* @__PURE__ */ new Date()).toISOString(),
29786
30860
  results: []
29787
30861
  };
29788
- mkdirSync12(this.dreamsDir, { recursive: true });
30862
+ mkdirSync13(this.dreamsDir, { recursive: true });
29789
30863
  this.saveDreamState();
29790
30864
  try {
29791
30865
  for (let cycle = 1; cycle <= totalCycles; cycle++) {
@@ -29838,8 +30912,8 @@ ${result.summary}`;
29838
30912
  if (mode !== "default" || cycle === totalCycles) {
29839
30913
  renderDreamContraction(cycle);
29840
30914
  const cycleSummary = this.buildCycleSummary(cycle, previousFindings);
29841
- const summaryPath = join36(this.dreamsDir, `cycle-${cycle}-summary.md`);
29842
- writeFileSync11(summaryPath, cycleSummary, "utf-8");
30915
+ const summaryPath = join37(this.dreamsDir, `cycle-${cycle}-summary.md`);
30916
+ writeFileSync12(summaryPath, cycleSummary, "utf-8");
29843
30917
  }
29844
30918
  if (mode === "lucid" && !this.abortController.signal.aborted) {
29845
30919
  this.saveVersionCheckpoint(cycle);
@@ -30051,7 +31125,7 @@ After synthesis, call task_complete with the final prioritized summary.`, toolMo
30051
31125
  }
30052
31126
  /** Build role-specific tool sets for swarm agents */
30053
31127
  buildSwarmTools(role, _workspace) {
30054
- const autoresearchDir = join36(this.repoRoot, ".oa", "autoresearch");
31128
+ const autoresearchDir = join37(this.repoRoot, ".oa", "autoresearch");
30055
31129
  const taskComplete = this.createSwarmTaskCompleteTool(role);
30056
31130
  switch (role) {
30057
31131
  case "researcher": {
@@ -30415,7 +31489,7 @@ INSTRUCTIONS:
30415
31489
  2. Summarize the key learnings and next steps
30416
31490
 
30417
31491
  Call task_complete with a human-readable summary of the autoresearch session.`, workspace, onEvent);
30418
- const reportPath = join36(this.dreamsDir, `cycle-${cycleNum}-autoresearch-report.md`);
31492
+ const reportPath = join37(this.dreamsDir, `cycle-${cycleNum}-autoresearch-report.md`);
30419
31493
  const report = `# Autoresearch Swarm Report \u2014 Cycle ${cycleNum}
30420
31494
 
30421
31495
  **Date**: ${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]}
@@ -30437,8 +31511,8 @@ ${summaryResult}
30437
31511
  *Generated by open-agents autoresearch swarm*
30438
31512
  `;
30439
31513
  try {
30440
- mkdirSync12(this.dreamsDir, { recursive: true });
30441
- writeFileSync11(reportPath, report, "utf-8");
31514
+ mkdirSync13(this.dreamsDir, { recursive: true });
31515
+ writeFileSync12(reportPath, report, "utf-8");
30442
31516
  } catch {
30443
31517
  }
30444
31518
  renderSwarmComplete(workspace);
@@ -30504,9 +31578,9 @@ ${summaryResult}
30504
31578
  }
30505
31579
  /** Save workspace backup for lucid mode */
30506
31580
  saveVersionCheckpoint(cycle) {
30507
- const checkpointDir = join36(this.dreamsDir, "checkpoints", `cycle-${cycle}`);
31581
+ const checkpointDir = join37(this.dreamsDir, "checkpoints", `cycle-${cycle}`);
30508
31582
  try {
30509
- mkdirSync12(checkpointDir, { recursive: true });
31583
+ mkdirSync13(checkpointDir, { recursive: true });
30510
31584
  try {
30511
31585
  const gitStatus = execSync23("git status --porcelain", {
30512
31586
  cwd: this.repoRoot,
@@ -30523,10 +31597,10 @@ ${summaryResult}
30523
31597
  encoding: "utf-8",
30524
31598
  timeout: 5e3
30525
31599
  }).trim();
30526
- writeFileSync11(join36(checkpointDir, "git-status.txt"), gitStatus, "utf-8");
30527
- writeFileSync11(join36(checkpointDir, "git-diff.patch"), gitDiff, "utf-8");
30528
- writeFileSync11(join36(checkpointDir, "git-hash.txt"), gitHash, "utf-8");
30529
- writeFileSync11(join36(checkpointDir, "checkpoint.json"), JSON.stringify({
31600
+ writeFileSync12(join37(checkpointDir, "git-status.txt"), gitStatus, "utf-8");
31601
+ writeFileSync12(join37(checkpointDir, "git-diff.patch"), gitDiff, "utf-8");
31602
+ writeFileSync12(join37(checkpointDir, "git-hash.txt"), gitHash, "utf-8");
31603
+ writeFileSync12(join37(checkpointDir, "checkpoint.json"), JSON.stringify({
30530
31604
  cycle,
30531
31605
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
30532
31606
  gitHash,
@@ -30534,7 +31608,7 @@ ${summaryResult}
30534
31608
  }, null, 2), "utf-8");
30535
31609
  renderInfo(`Checkpoint saved: cycle ${cycle} (${gitHash.slice(0, 8)})`);
30536
31610
  } catch {
30537
- writeFileSync11(join36(checkpointDir, "checkpoint.json"), JSON.stringify({ cycle, timestamp: (/* @__PURE__ */ new Date()).toISOString(), mode: this.state.mode }, null, 2), "utf-8");
31611
+ writeFileSync12(join37(checkpointDir, "checkpoint.json"), JSON.stringify({ cycle, timestamp: (/* @__PURE__ */ new Date()).toISOString(), mode: this.state.mode }, null, 2), "utf-8");
30538
31612
  renderInfo(`Checkpoint saved: cycle ${cycle} (no git)`);
30539
31613
  }
30540
31614
  } catch (err) {
@@ -30592,14 +31666,14 @@ ${files.map((f) => `- [\`${f}\`](./${f})`).join("\n")}
30592
31666
  ---
30593
31667
  *Auto-generated by open-agents dream engine*
30594
31668
  `;
30595
- writeFileSync11(join36(this.dreamsDir, "PROPOSAL-INDEX.md"), index, "utf-8");
31669
+ writeFileSync12(join37(this.dreamsDir, "PROPOSAL-INDEX.md"), index, "utf-8");
30596
31670
  } catch {
30597
31671
  }
30598
31672
  }
30599
31673
  /** Save dream state for resume/inspection */
30600
31674
  saveDreamState() {
30601
31675
  try {
30602
- writeFileSync11(join36(this.dreamsDir, "dream-state.json"), JSON.stringify(this.state, null, 2) + "\n", "utf-8");
31676
+ writeFileSync12(join37(this.dreamsDir, "dream-state.json"), JSON.stringify(this.state, null, 2) + "\n", "utf-8");
30603
31677
  } catch {
30604
31678
  }
30605
31679
  }
@@ -30752,8 +31826,8 @@ var init_bless_engine = __esm({
30752
31826
  });
30753
31827
 
30754
31828
  // packages/cli/dist/tui/dmn-engine.js
30755
- import { existsSync as existsSync27, readFileSync as readFileSync20, writeFileSync as writeFileSync12, mkdirSync as mkdirSync13, readdirSync as readdirSync11, unlinkSync as unlinkSync5 } from "node:fs";
30756
- import { join as join37, basename as basename13 } from "node:path";
31829
+ import { existsSync as existsSync28, readFileSync as readFileSync21, writeFileSync as writeFileSync13, mkdirSync as mkdirSync14, readdirSync as readdirSync11, unlinkSync as unlinkSync5 } from "node:fs";
31830
+ import { join as join38, basename as basename13 } from "node:path";
30757
31831
  function buildDMNGatherPrompt(recentTaskSummaries, dueReminders, attentionItems, memoryTopics, capabilities, competence, reflectionBuffer) {
30758
31832
  const competenceReport = competence.length > 0 ? competence.map((c3) => {
30759
31833
  const rate = c3.attempts > 0 ? Math.round(c3.successes / c3.attempts * 100) : 0;
@@ -30985,9 +32059,9 @@ var init_dmn_engine = __esm({
30985
32059
  constructor(config, repoRoot) {
30986
32060
  this.config = config;
30987
32061
  this.repoRoot = repoRoot;
30988
- this.stateDir = join37(repoRoot, ".oa", "dmn");
30989
- this.historyDir = join37(repoRoot, ".oa", "dmn", "cycles");
30990
- mkdirSync13(this.historyDir, { recursive: true });
32062
+ this.stateDir = join38(repoRoot, ".oa", "dmn");
32063
+ this.historyDir = join38(repoRoot, ".oa", "dmn", "cycles");
32064
+ mkdirSync14(this.historyDir, { recursive: true });
30991
32065
  this.loadState();
30992
32066
  }
30993
32067
  get stats() {
@@ -31576,11 +32650,11 @@ OUTPUT: Call task_complete with JSON:
31576
32650
  async gatherMemoryTopics() {
31577
32651
  const topics = [];
31578
32652
  const dirs = [
31579
- join37(this.repoRoot, ".oa", "memory"),
31580
- join37(this.repoRoot, ".open-agents", "memory")
32653
+ join38(this.repoRoot, ".oa", "memory"),
32654
+ join38(this.repoRoot, ".open-agents", "memory")
31581
32655
  ];
31582
32656
  for (const dir of dirs) {
31583
- if (!existsSync27(dir))
32657
+ if (!existsSync28(dir))
31584
32658
  continue;
31585
32659
  try {
31586
32660
  const files = readdirSync11(dir).filter((f) => f.endsWith(".json"));
@@ -31596,29 +32670,29 @@ OUTPUT: Call task_complete with JSON:
31596
32670
  }
31597
32671
  // ── State persistence ─────────────────────────────────────────────────
31598
32672
  loadState() {
31599
- const path = join37(this.stateDir, "state.json");
31600
- if (existsSync27(path)) {
32673
+ const path = join38(this.stateDir, "state.json");
32674
+ if (existsSync28(path)) {
31601
32675
  try {
31602
- this.state = JSON.parse(readFileSync20(path, "utf-8"));
32676
+ this.state = JSON.parse(readFileSync21(path, "utf-8"));
31603
32677
  } catch {
31604
32678
  }
31605
32679
  }
31606
32680
  }
31607
32681
  saveState() {
31608
32682
  try {
31609
- writeFileSync12(join37(this.stateDir, "state.json"), JSON.stringify(this.state, null, 2) + "\n", "utf-8");
32683
+ writeFileSync13(join38(this.stateDir, "state.json"), JSON.stringify(this.state, null, 2) + "\n", "utf-8");
31610
32684
  } catch {
31611
32685
  }
31612
32686
  }
31613
32687
  saveCycleResult(result) {
31614
32688
  try {
31615
32689
  const filename = `cycle-${result.cycleNumber}-${Date.now()}.json`;
31616
- writeFileSync12(join37(this.historyDir, filename), JSON.stringify(result, null, 2) + "\n", "utf-8");
32690
+ writeFileSync13(join38(this.historyDir, filename), JSON.stringify(result, null, 2) + "\n", "utf-8");
31617
32691
  const files = readdirSync11(this.historyDir).filter((f) => f.startsWith("cycle-") && f.endsWith(".json")).sort();
31618
32692
  if (files.length > 50) {
31619
32693
  for (const old of files.slice(0, files.length - 50)) {
31620
32694
  try {
31621
- unlinkSync5(join37(this.historyDir, old));
32695
+ unlinkSync5(join38(this.historyDir, old));
31622
32696
  } catch {
31623
32697
  }
31624
32698
  }
@@ -31631,8 +32705,8 @@ OUTPUT: Call task_complete with JSON:
31631
32705
  });
31632
32706
 
31633
32707
  // packages/cli/dist/tui/snr-engine.js
31634
- import { existsSync as existsSync28, readdirSync as readdirSync12, readFileSync as readFileSync21 } from "node:fs";
31635
- import { join as join38, basename as basename14 } from "node:path";
32708
+ import { existsSync as existsSync29, readdirSync as readdirSync12, readFileSync as readFileSync22 } from "node:fs";
32709
+ import { join as join39, basename as basename14 } from "node:path";
31636
32710
  function computeDPrime(signalScores, noiseScores) {
31637
32711
  if (signalScores.length === 0 || noiseScores.length === 0)
31638
32712
  return 0;
@@ -31879,11 +32953,11 @@ Call task_complete with the JSON array when done.`, onEvent)
31879
32953
  loadMemoryEntries(topics) {
31880
32954
  const entries = [];
31881
32955
  const dirs = [
31882
- join38(this.repoRoot, ".oa", "memory"),
31883
- join38(this.repoRoot, ".open-agents", "memory")
32956
+ join39(this.repoRoot, ".oa", "memory"),
32957
+ join39(this.repoRoot, ".open-agents", "memory")
31884
32958
  ];
31885
32959
  for (const dir of dirs) {
31886
- if (!existsSync28(dir))
32960
+ if (!existsSync29(dir))
31887
32961
  continue;
31888
32962
  try {
31889
32963
  const files = readdirSync12(dir).filter((f) => f.endsWith(".json"));
@@ -31892,7 +32966,7 @@ Call task_complete with the JSON array when done.`, onEvent)
31892
32966
  if (topics.length > 0 && !topics.includes(topic))
31893
32967
  continue;
31894
32968
  try {
31895
- const data = JSON.parse(readFileSync21(join38(dir, f), "utf-8"));
32969
+ const data = JSON.parse(readFileSync22(join39(dir, f), "utf-8"));
31896
32970
  for (const [key, val] of Object.entries(data)) {
31897
32971
  const value = typeof val === "object" && val !== null && "value" in val ? String(val.value) : String(val);
31898
32972
  entries.push({ topic, key, value });
@@ -32345,8 +33419,8 @@ var init_tool_policy = __esm({
32345
33419
  });
32346
33420
 
32347
33421
  // packages/cli/dist/tui/telegram-bridge.js
32348
- import { mkdirSync as mkdirSync14, existsSync as existsSync29, unlinkSync as unlinkSync6, readdirSync as readdirSync13, statSync as statSync10 } from "node:fs";
32349
- import { join as join39, resolve as resolve24 } from "node:path";
33422
+ import { mkdirSync as mkdirSync15, existsSync as existsSync30, unlinkSync as unlinkSync6, readdirSync as readdirSync13, statSync as statSync10 } from "node:fs";
33423
+ import { join as join40, resolve as resolve24 } from "node:path";
32350
33424
  import { writeFile as writeFileAsync } from "node:fs/promises";
32351
33425
  function convertMarkdownToTelegramHTML(md) {
32352
33426
  let html = md;
@@ -32674,7 +33748,7 @@ with summary "no_reply" to silently skip without responding.
32674
33748
  this.polling = true;
32675
33749
  this.abortController = new AbortController();
32676
33750
  try {
32677
- mkdirSync14(this.mediaCacheDir, { recursive: true });
33751
+ mkdirSync15(this.mediaCacheDir, { recursive: true });
32678
33752
  } catch {
32679
33753
  }
32680
33754
  this.mediaCacheCleanupTimer = setInterval(() => this.cleanupMediaCache(), 5 * 60 * 1e3);
@@ -33098,7 +34172,7 @@ Telegram admin: @${msg.username}` : `Telegram ${isGroup ? "group" : "public"} ch
33098
34172
  return null;
33099
34173
  const buffer = Buffer.from(await res.arrayBuffer());
33100
34174
  const fileName = `${Date.now()}-${fileId.slice(0, 8)}${extension}`;
33101
- const localPath = join39(this.mediaCacheDir, fileName);
34175
+ const localPath = join40(this.mediaCacheDir, fileName);
33102
34176
  await writeFileAsync(localPath, buffer);
33103
34177
  return localPath;
33104
34178
  } catch {
@@ -34820,11 +35894,11 @@ var init_status_bar = __esm({
34820
35894
  import * as readline2 from "node:readline";
34821
35895
  import { Writable } from "node:stream";
34822
35896
  import { cwd } from "node:process";
34823
- import { resolve as resolve25, join as join40, dirname as dirname12, extname as extname9 } from "node:path";
35897
+ import { resolve as resolve25, join as join41, dirname as dirname13, extname as extname9 } from "node:path";
34824
35898
  import { createRequire as createRequire2 } from "node:module";
34825
35899
  import { fileURLToPath as fileURLToPath9 } from "node:url";
34826
- import { readFileSync as readFileSync22, rmSync as rmSync2, readdirSync as readdirSync14 } from "node:fs";
34827
- import { existsSync as existsSync30 } from "node:fs";
35900
+ import { readFileSync as readFileSync23, rmSync as rmSync2, readdirSync as readdirSync14 } from "node:fs";
35901
+ import { existsSync as existsSync31 } from "node:fs";
34828
35902
  function formatTimeAgo(date) {
34829
35903
  const seconds = Math.floor((Date.now() - date.getTime()) / 1e3);
34830
35904
  if (seconds < 60)
@@ -34841,14 +35915,14 @@ function formatTimeAgo(date) {
34841
35915
  function getVersion() {
34842
35916
  try {
34843
35917
  const require2 = createRequire2(import.meta.url);
34844
- const thisDir = dirname12(fileURLToPath9(import.meta.url));
35918
+ const thisDir = dirname13(fileURLToPath9(import.meta.url));
34845
35919
  const candidates = [
34846
- join40(thisDir, "..", "package.json"),
34847
- join40(thisDir, "..", "..", "package.json"),
34848
- join40(thisDir, "..", "..", "..", "package.json")
35920
+ join41(thisDir, "..", "package.json"),
35921
+ join41(thisDir, "..", "..", "package.json"),
35922
+ join41(thisDir, "..", "..", "..", "package.json")
34849
35923
  ];
34850
35924
  for (const pkgPath of candidates) {
34851
- if (existsSync30(pkgPath)) {
35925
+ if (existsSync31(pkgPath)) {
34852
35926
  const pkg = require2(pkgPath);
34853
35927
  if (pkg.name === "open-agents-ai" || pkg.name === "@open-agents/cli") {
34854
35928
  return pkg.version ?? "0.0.0";
@@ -35042,15 +36116,15 @@ Use task_status("${taskId}") or task_output("${taskId}") to check progress.`
35042
36116
  function gatherMemorySnippets(root) {
35043
36117
  const snippets = [];
35044
36118
  const dirs = [
35045
- join40(root, ".oa", "memory"),
35046
- join40(root, ".open-agents", "memory")
36119
+ join41(root, ".oa", "memory"),
36120
+ join41(root, ".open-agents", "memory")
35047
36121
  ];
35048
36122
  for (const dir of dirs) {
35049
- if (!existsSync30(dir))
36123
+ if (!existsSync31(dir))
35050
36124
  continue;
35051
36125
  try {
35052
36126
  for (const f of readdirSync14(dir).filter((f2) => f2.endsWith(".json"))) {
35053
- const data = JSON.parse(readFileSync22(join40(dir, f), "utf-8"));
36127
+ const data = JSON.parse(readFileSync23(join41(dir, f), "utf-8"));
35054
36128
  for (const val of Object.values(data)) {
35055
36129
  const v = typeof val === "object" && val !== null && "value" in val ? String(val.value) : String(val);
35056
36130
  if (v.length > 10)
@@ -35663,6 +36737,9 @@ async function startInteractive(config, repoPath) {
35663
36737
  const voiceEngine = new VoiceEngine();
35664
36738
  let voiceSession = null;
35665
36739
  let exposeGateway = null;
36740
+ let peerMesh = null;
36741
+ let inferenceRouter = null;
36742
+ const secretVault = new SecretVault(join41(repoRoot, ".oa", "vault.enc"));
35666
36743
  let adminSessionKey = null;
35667
36744
  const callSubAgents = /* @__PURE__ */ new Map();
35668
36745
  const streamRenderer = new StreamRenderer();
@@ -35824,6 +36901,9 @@ Rationale: ${proposal.rationale}${provenanceNote}`;
35824
36901
  "/stop",
35825
36902
  "/resume",
35826
36903
  "/expose",
36904
+ "/p2p",
36905
+ "/secrets",
36906
+ "/vault",
35827
36907
  "/full-send-bless",
35828
36908
  "/bless",
35829
36909
  "/telegram",
@@ -36559,6 +37639,80 @@ Respond concisely and safely. Remember: you are talking to the general public.`;
36559
37639
  stats: exposeGateway.formatStats()
36560
37640
  };
36561
37641
  },
37642
+ // ── P2P mesh controls ────────────────────────────────────────────────
37643
+ async p2pStart(bootstrapPeers) {
37644
+ if (peerMesh)
37645
+ throw new Error("P2P mesh is already running");
37646
+ let models = [];
37647
+ try {
37648
+ const resp = await fetch(`${commandCtx.config.backendUrl ?? "http://127.0.0.1:11434"}/api/tags`);
37649
+ const data = await resp.json();
37650
+ models = data.models?.map((m) => m.name) ?? [];
37651
+ } catch {
37652
+ }
37653
+ peerMesh = new PeerMesh({
37654
+ displayName: commandCtx.config.model ?? "open-agents-node",
37655
+ capabilities: { models, gpuVramGB: 0, teeAttested: false, maxContextTokens: 32768, load: 0 },
37656
+ bootstrapPeers
37657
+ });
37658
+ const port = await peerMesh.start();
37659
+ inferenceRouter = new InferenceRouter({
37660
+ mesh: peerMesh,
37661
+ vault: secretVault
37662
+ });
37663
+ return { peerId: peerMesh.peerId, port };
37664
+ },
37665
+ async p2pStop() {
37666
+ if (peerMesh) {
37667
+ await peerMesh.stop();
37668
+ peerMesh = null;
37669
+ inferenceRouter = null;
37670
+ }
37671
+ },
37672
+ async p2pConnect(endpoint, trust) {
37673
+ if (!peerMesh)
37674
+ throw new Error("P2P mesh is not running \u2014 use /p2p start first");
37675
+ const trustTier = trust ?? "public";
37676
+ const peer = await peerMesh.connectToPeer(endpoint, trustTier);
37677
+ return peer?.peerId ?? null;
37678
+ },
37679
+ p2pStatus() {
37680
+ if (!peerMesh)
37681
+ return "P2P mesh is not running.";
37682
+ const peers = peerMesh.getPeers();
37683
+ const connected = peers.filter((p) => p.connected);
37684
+ const lines = [
37685
+ `Node: ${peerMesh.peerId} (port ${peerMesh.port})`,
37686
+ `Peers: ${connected.length} connected / ${peers.length} known`
37687
+ ];
37688
+ if (inferenceRouter) {
37689
+ const stats = inferenceRouter.getStats();
37690
+ lines.push(`Routed: ${stats.totalRouted} | Redacted: ${stats.totalRedacted} | Errors: ${stats.totalErrors}`);
37691
+ if (stats.availableModels.length > 0) {
37692
+ lines.push(`Models: ${stats.availableModels.join(", ")}`);
37693
+ }
37694
+ }
37695
+ for (const p of connected) {
37696
+ lines.push(` ${p.peerId.slice(0, 8)}\u2026 [${p.trust}] ${p.latencyMs}ms \u2014 ${p.capabilities.models.join(", ") || "no models"}`);
37697
+ }
37698
+ return lines.join("\n");
37699
+ },
37700
+ isP2PActive() {
37701
+ return peerMesh !== null;
37702
+ },
37703
+ // ── Secret vault controls ────────────────────────────────────────────
37704
+ secretSet(name, value, minTrust) {
37705
+ secretVault.set(name, value, minTrust ?? "verified");
37706
+ },
37707
+ secretDelete(name) {
37708
+ return secretVault.delete(name);
37709
+ },
37710
+ secretList() {
37711
+ return secretVault.list();
37712
+ },
37713
+ secretImportEnv() {
37714
+ return secretVault.importFromEnv();
37715
+ },
36562
37716
  async sendCallButton(chatId, url) {
36563
37717
  if (telegramBridge?.isActive) {
36564
37718
  await telegramBridge.sendCallButton(chatId, url);
@@ -36628,8 +37782,8 @@ Respond concisely and safely. Remember: you are talking to the general public.`;
36628
37782
  return true;
36629
37783
  },
36630
37784
  destroyProject() {
36631
- const oaPath = join40(repoRoot, OA_DIR);
36632
- if (existsSync30(oaPath)) {
37785
+ const oaPath = join41(repoRoot, OA_DIR);
37786
+ if (existsSync31(oaPath)) {
36633
37787
  try {
36634
37788
  rmSync2(oaPath, { recursive: true, force: true });
36635
37789
  writeContent(() => renderInfo(`Removed ${OA_DIR}/ directory.`));
@@ -36913,13 +38067,13 @@ Execute this skill now. Follow the behavioral guidance above.`;
36913
38067
  }
36914
38068
  }
36915
38069
  const cleanPath = input.replace(/^['"]|['"]$/g, "").trim();
36916
- const isImage = isImagePath(cleanPath) && existsSync30(resolve25(repoRoot, cleanPath));
36917
- const isMedia = !isImage && isTranscribablePath(cleanPath) && existsSync30(resolve25(repoRoot, cleanPath));
38070
+ const isImage = isImagePath(cleanPath) && existsSync31(resolve25(repoRoot, cleanPath));
38071
+ const isMedia = !isImage && isTranscribablePath(cleanPath) && existsSync31(resolve25(repoRoot, cleanPath));
36918
38072
  if (activeTask) {
36919
38073
  if (isImage) {
36920
38074
  try {
36921
38075
  const imgPath = resolve25(repoRoot, cleanPath);
36922
- const imgBuffer = readFileSync22(imgPath);
38076
+ const imgBuffer = readFileSync23(imgPath);
36923
38077
  const base64 = imgBuffer.toString("base64");
36924
38078
  const ext = extname9(cleanPath).toLowerCase();
36925
38079
  const mime = ext === ".png" ? "image/png" : ext === ".gif" ? "image/gif" : ext === ".webp" ? "image/webp" : "image/jpeg";
@@ -37102,13 +38256,13 @@ NEW TASK: ${fullInput}`;
37102
38256
  writeContent(() => renderError(errMsg));
37103
38257
  if (failureStore) {
37104
38258
  try {
37105
- const { createHash: createHash2 } = await import("node:crypto");
38259
+ const { createHash: createHash4 } = await import("node:crypto");
37106
38260
  failureStore.insert({
37107
38261
  taskId: "",
37108
38262
  sessionId: `${Date.now()}`,
37109
38263
  repoRoot,
37110
38264
  failureType: "runtime-error",
37111
- fingerprint: createHash2("sha256").update(errMsg.slice(0, 200)).digest("hex").slice(0, 16),
38265
+ fingerprint: createHash4("sha256").update(errMsg.slice(0, 200)).digest("hex").slice(0, 16),
37112
38266
  filePath: null,
37113
38267
  errorMessage: errMsg.slice(0, 500),
37114
38268
  context: null,
@@ -37229,6 +38383,12 @@ Rationale: ${proposal.rationale}${provenanceNote}`;
37229
38383
  exposeGateway.stop().catch(() => {
37230
38384
  });
37231
38385
  }
38386
+ if (peerMesh) {
38387
+ peerMesh.stop().catch(() => {
38388
+ });
38389
+ peerMesh = null;
38390
+ inferenceRouter = null;
38391
+ }
37232
38392
  statusBar.deactivate();
37233
38393
  const closeRows = process.stdout.rows ?? 24;
37234
38394
  process.stdout.write(`\x1B[${closeRows - 4};1H\x1B[J
@@ -37318,6 +38478,7 @@ var init_interactive = __esm({
37318
38478
  init_listen();
37319
38479
  init_voice_session();
37320
38480
  init_expose();
38481
+ init_p2p();
37321
38482
  init_call_agent();
37322
38483
  init_config();
37323
38484
  init_updater();
@@ -37375,8 +38536,8 @@ var init_run = __esm({
37375
38536
  import { glob } from "glob";
37376
38537
  import ignore from "ignore";
37377
38538
  import { readFile as readFile14, stat as stat4 } from "node:fs/promises";
37378
- import { createHash } from "node:crypto";
37379
- import { join as join41, relative as relative3, extname as extname10, basename as basename15 } from "node:path";
38539
+ import { createHash as createHash3 } from "node:crypto";
38540
+ import { join as join42, relative as relative3, extname as extname10, basename as basename15 } from "node:path";
37380
38541
  var DEFAULT_EXCLUDE, LANGUAGE_MAP, CodebaseIndexer;
37381
38542
  var init_codebase_indexer = __esm({
37382
38543
  "packages/indexer/dist/codebase-indexer.js"() {
@@ -37420,7 +38581,7 @@ var init_codebase_indexer = __esm({
37420
38581
  const ig = ignore.default();
37421
38582
  if (this.config.respectGitignore) {
37422
38583
  try {
37423
- const gitignoreContent = await readFile14(join41(this.config.rootDir, ".gitignore"), "utf-8");
38584
+ const gitignoreContent = await readFile14(join42(this.config.rootDir, ".gitignore"), "utf-8");
37424
38585
  ig.add(gitignoreContent);
37425
38586
  } catch {
37426
38587
  }
@@ -37435,13 +38596,13 @@ var init_codebase_indexer = __esm({
37435
38596
  for (const relativePath of files) {
37436
38597
  if (ig.ignores(relativePath))
37437
38598
  continue;
37438
- const fullPath = join41(this.config.rootDir, relativePath);
38599
+ const fullPath = join42(this.config.rootDir, relativePath);
37439
38600
  try {
37440
38601
  const fileStat = await stat4(fullPath);
37441
38602
  if (fileStat.size > this.config.maxFileSize)
37442
38603
  continue;
37443
38604
  const content = await readFile14(fullPath);
37444
- const hash = createHash("sha256").update(content).digest("hex");
38605
+ const hash = createHash3("sha256").update(content).digest("hex");
37445
38606
  const ext = extname10(relativePath);
37446
38607
  indexed.push({
37447
38608
  path: fullPath,
@@ -37481,7 +38642,7 @@ var init_codebase_indexer = __esm({
37481
38642
  if (!child) {
37482
38643
  child = {
37483
38644
  name: part,
37484
- path: join41(current.path, part),
38645
+ path: join42(current.path, part),
37485
38646
  type: "directory",
37486
38647
  children: []
37487
38648
  };
@@ -37564,13 +38725,13 @@ __export(index_repo_exports, {
37564
38725
  indexRepoCommand: () => indexRepoCommand
37565
38726
  });
37566
38727
  import { resolve as resolve26 } from "node:path";
37567
- import { existsSync as existsSync31, statSync as statSync11 } from "node:fs";
38728
+ import { existsSync as existsSync32, statSync as statSync11 } from "node:fs";
37568
38729
  import { cwd as cwd2 } from "node:process";
37569
38730
  async function indexRepoCommand(opts, _config) {
37570
38731
  const repoRoot = resolve26(opts.repoPath ?? cwd2());
37571
38732
  printHeader("Index Repository");
37572
38733
  printInfo(`Indexing: ${repoRoot}`);
37573
- if (!existsSync31(repoRoot)) {
38734
+ if (!existsSync32(repoRoot)) {
37574
38735
  printError(`Path does not exist: ${repoRoot}`);
37575
38736
  process.exit(1);
37576
38737
  }
@@ -37822,7 +38983,7 @@ var config_exports = {};
37822
38983
  __export(config_exports, {
37823
38984
  configCommand: () => configCommand
37824
38985
  });
37825
- import { join as join42, resolve as resolve27 } from "node:path";
38986
+ import { join as join43, resolve as resolve27 } from "node:path";
37826
38987
  import { homedir as homedir13 } from "node:os";
37827
38988
  import { cwd as cwd3 } from "node:process";
37828
38989
  function redactIfSensitive(key, value) {
@@ -37881,7 +39042,7 @@ function handleShow(opts, config) {
37881
39042
  }
37882
39043
  }
37883
39044
  printSection("Config File");
37884
- printInfo(`~/.open-agents/config.json (${join42(homedir13(), ".open-agents", "config.json")})`);
39045
+ printInfo(`~/.open-agents/config.json (${join43(homedir13(), ".open-agents", "config.json")})`);
37885
39046
  printSection("Priority Chain");
37886
39047
  printInfo(" 1. CLI flags (--model, --backend-url, etc.)");
37887
39048
  printInfo(" 2. Project .oa/settings.json (--local)");
@@ -37920,7 +39081,7 @@ function handleSet(opts, _config) {
37920
39081
  const coerced = coerceForSettings(key, value);
37921
39082
  saveProjectSettings(repoRoot, { [key]: coerced });
37922
39083
  printSuccess(`Project override set: ${key} = ${redactIfSensitive(key, value)}`);
37923
- printInfo(`Saved to ${join42(repoRoot, ".oa", "settings.json")}`);
39084
+ printInfo(`Saved to ${join43(repoRoot, ".oa", "settings.json")}`);
37924
39085
  printInfo("This override applies only when running in this workspace.");
37925
39086
  } catch (err) {
37926
39087
  printError(`Failed to save: ${err instanceof Error ? err.message : String(err)}`);
@@ -38139,8 +39300,8 @@ __export(eval_exports, {
38139
39300
  evalCommand: () => evalCommand
38140
39301
  });
38141
39302
  import { tmpdir as tmpdir7 } from "node:os";
38142
- import { mkdirSync as mkdirSync15, writeFileSync as writeFileSync13 } from "node:fs";
38143
- import { join as join43 } from "node:path";
39303
+ import { mkdirSync as mkdirSync16, writeFileSync as writeFileSync14 } from "node:fs";
39304
+ import { join as join44 } from "node:path";
38144
39305
  async function evalCommand(opts, config) {
38145
39306
  const suiteName = opts.suite ?? "basic";
38146
39307
  const suite = SUITES[suiteName];
@@ -38261,9 +39422,9 @@ async function evalCommand(opts, config) {
38261
39422
  process.exit(failed > 0 ? 1 : 0);
38262
39423
  }
38263
39424
  function createTempEvalRepo() {
38264
- const dir = join43(tmpdir7(), `open-agents-eval-${Date.now()}`);
38265
- mkdirSync15(dir, { recursive: true });
38266
- writeFileSync13(join43(dir, "package.json"), JSON.stringify({ name: "eval-repo", version: "0.0.0" }, null, 2) + "\n", "utf8");
39425
+ const dir = join44(tmpdir7(), `open-agents-eval-${Date.now()}`);
39426
+ mkdirSync16(dir, { recursive: true });
39427
+ writeFileSync14(join44(dir, "package.json"), JSON.stringify({ name: "eval-repo", version: "0.0.0" }, null, 2) + "\n", "utf8");
38267
39428
  return dir;
38268
39429
  }
38269
39430
  var BASIC_SUITE, FULL_SUITE, SUITES;
@@ -38323,7 +39484,7 @@ init_updater();
38323
39484
  import { parseArgs as nodeParseArgs2 } from "node:util";
38324
39485
  import { createRequire as createRequire3 } from "node:module";
38325
39486
  import { fileURLToPath as fileURLToPath10 } from "node:url";
38326
- import { dirname as dirname13, join as join44 } from "node:path";
39487
+ import { dirname as dirname14, join as join45 } from "node:path";
38327
39488
 
38328
39489
  // packages/cli/dist/cli.js
38329
39490
  import { createInterface } from "node:readline";
@@ -38430,7 +39591,7 @@ init_output();
38430
39591
  function getVersion2() {
38431
39592
  try {
38432
39593
  const require2 = createRequire3(import.meta.url);
38433
- const pkgPath = join44(dirname13(fileURLToPath10(import.meta.url)), "..", "package.json");
39594
+ const pkgPath = join45(dirname14(fileURLToPath10(import.meta.url)), "..", "package.json");
38434
39595
  const pkg = require2(pkgPath);
38435
39596
  return pkg.version;
38436
39597
  } catch {