open-agents-ai 0.67.1 → 0.68.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +1462 -320
- 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:
|
|
14630
|
-
const { join:
|
|
14631
|
-
const sessionDir =
|
|
14632
|
-
|
|
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
|
-
|
|
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
|
|
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:
|
|
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
|
|
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 =
|
|
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 =
|
|
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
|
|
20161
|
+
var EventEmitter7 = __require("events");
|
|
20162
20162
|
var http = __require("http");
|
|
20163
20163
|
var { Duplex } = __require("stream");
|
|
20164
|
-
var { createHash:
|
|
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
|
|
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 =
|
|
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/
|
|
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
|
|
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
|
|
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 =
|
|
23116
|
-
_packageRoot =
|
|
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
|
|
23122
|
-
import { join as
|
|
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 =
|
|
24075
|
+
const oaPath = join31(repoRoot, OA_DIR);
|
|
23126
24076
|
for (const sub of SUBDIRS) {
|
|
23127
|
-
|
|
24077
|
+
mkdirSync8(join31(oaPath, sub), { recursive: true });
|
|
23128
24078
|
}
|
|
23129
24079
|
try {
|
|
23130
|
-
const gitignorePath =
|
|
24080
|
+
const gitignorePath = join31(repoRoot, ".gitignore");
|
|
23131
24081
|
const settingsPattern = ".oa/settings.json";
|
|
23132
|
-
if (
|
|
23133
|
-
const content =
|
|
24082
|
+
if (existsSync22(gitignorePath)) {
|
|
24083
|
+
const content = readFileSync16(gitignorePath, "utf-8");
|
|
23134
24084
|
if (!content.includes(settingsPattern)) {
|
|
23135
|
-
|
|
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
|
|
24093
|
+
return existsSync22(join31(repoRoot, OA_DIR, "index"));
|
|
23144
24094
|
}
|
|
23145
24095
|
function loadProjectSettings(repoRoot) {
|
|
23146
|
-
const settingsPath =
|
|
24096
|
+
const settingsPath = join31(repoRoot, OA_DIR, "settings.json");
|
|
23147
24097
|
try {
|
|
23148
|
-
if (
|
|
23149
|
-
return JSON.parse(
|
|
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 =
|
|
23157
|
-
|
|
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
|
-
|
|
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 =
|
|
24113
|
+
const settingsPath = join31(homedir9(), ".open-agents", "settings.json");
|
|
23164
24114
|
try {
|
|
23165
|
-
if (
|
|
23166
|
-
return JSON.parse(
|
|
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 =
|
|
23174
|
-
|
|
24123
|
+
const dir = join31(homedir9(), ".open-agents");
|
|
24124
|
+
mkdirSync8(dir, { recursive: true });
|
|
23175
24125
|
const existing = loadGlobalSettings();
|
|
23176
24126
|
const merged = { ...existing, ...settings };
|
|
23177
|
-
|
|
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 =
|
|
24142
|
+
const filePath = join31(dir, name);
|
|
23193
24143
|
const normalizedName = name.toLowerCase();
|
|
23194
|
-
if (
|
|
24144
|
+
if (existsSync22(filePath) && !seen.has(filePath)) {
|
|
23195
24145
|
seen.add(filePath);
|
|
23196
24146
|
try {
|
|
23197
|
-
let content =
|
|
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 =
|
|
23212
|
-
if (
|
|
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 =
|
|
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 =
|
|
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 =
|
|
24195
|
+
const metaPath = join31(repoRoot, OA_DIR, "index", "meta.json");
|
|
23246
24196
|
try {
|
|
23247
|
-
return JSON.parse(
|
|
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 =
|
|
23299
|
-
|
|
23300
|
-
|
|
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 =
|
|
23305
|
-
|
|
23306
|
-
|
|
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 =
|
|
23310
|
-
if (!
|
|
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(
|
|
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(
|
|
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 =
|
|
23330
|
-
|
|
23331
|
-
|
|
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 =
|
|
24284
|
+
const filePath = join31(repoRoot, OA_DIR, "history", PENDING_TASK_FILE);
|
|
23335
24285
|
try {
|
|
23336
|
-
if (!
|
|
24286
|
+
if (!existsSync22(filePath))
|
|
23337
24287
|
return null;
|
|
23338
|
-
const data = JSON.parse(
|
|
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 =
|
|
23350
|
-
|
|
23351
|
-
const filePath =
|
|
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 (
|
|
23355
|
-
ctx = JSON.parse(
|
|
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
|
-
|
|
24317
|
+
writeFileSync8(filePath, JSON.stringify(ctx, null, 2) + "\n", "utf-8");
|
|
23368
24318
|
}
|
|
23369
24319
|
function loadSessionContext(repoRoot) {
|
|
23370
|
-
const filePath =
|
|
24320
|
+
const filePath = join31(repoRoot, OA_DIR, "context", CONTEXT_SAVE_FILE);
|
|
23371
24321
|
try {
|
|
23372
|
-
if (!
|
|
24322
|
+
if (!existsSync22(filePath))
|
|
23373
24323
|
return null;
|
|
23374
|
-
return JSON.parse(
|
|
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 =
|
|
23422
|
-
if (
|
|
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(
|
|
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 (
|
|
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(
|
|
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(
|
|
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
|
|
23541
|
-
import { join as
|
|
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;
|
|
@@ -23712,28 +24662,17 @@ function installOllamaLinux() {
|
|
|
23712
24662
|
return false;
|
|
23713
24663
|
}
|
|
23714
24664
|
}
|
|
23715
|
-
async function installOllamaMac(
|
|
24665
|
+
async function installOllamaMac(_rl) {
|
|
23716
24666
|
if (!hasCmd("brew")) {
|
|
23717
24667
|
process.stdout.write(`
|
|
23718
|
-
${c2.
|
|
23719
|
-
|
|
23720
|
-
`);
|
|
23721
|
-
const installBrew = await ask(rl, ` ${c2.bold("Install Homebrew?")} (Y/n) `);
|
|
23722
|
-
if (installBrew.toLowerCase() === "n") {
|
|
23723
|
-
process.stdout.write(` ${c2.dim("Skipping Homebrew install.")}
|
|
23724
|
-
|
|
23725
|
-
`);
|
|
23726
|
-
return false;
|
|
23727
|
-
}
|
|
23728
|
-
process.stdout.write(`
|
|
23729
|
-
${c2.cyan("\u25CF")} Installing Homebrew...
|
|
24668
|
+
${c2.cyan("\u25CF")} Homebrew not found. Installing automatically (needed for Ollama on macOS)...
|
|
23730
24669
|
|
|
23731
24670
|
`);
|
|
23732
24671
|
try {
|
|
23733
24672
|
execSync20('/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"', { stdio: "inherit", timeout: 6e5 });
|
|
23734
24673
|
if (!hasCmd("brew")) {
|
|
23735
24674
|
try {
|
|
23736
|
-
const brewPrefix =
|
|
24675
|
+
const brewPrefix = existsSync23("/opt/homebrew/bin/brew") ? "/opt/homebrew" : "/usr/local";
|
|
23737
24676
|
process.env["PATH"] = `${brewPrefix}/bin:${process.env["PATH"]}`;
|
|
23738
24677
|
} catch {
|
|
23739
24678
|
}
|
|
@@ -24013,13 +24952,34 @@ async function doSetup(config, rl) {
|
|
|
24013
24952
|
`);
|
|
24014
24953
|
const ollamaInstalled = hasCmd("ollama");
|
|
24015
24954
|
if (ollamaInstalled) {
|
|
24016
|
-
process.stdout.write(` ${c2.cyan("\u25CF")} Ollama is installed but not running.
|
|
24955
|
+
process.stdout.write(` ${c2.cyan("\u25CF")} Ollama is installed but not running. Starting automatically...
|
|
24956
|
+
`);
|
|
24957
|
+
try {
|
|
24958
|
+
const child = spawn12("ollama", ["serve"], { stdio: "ignore", detached: true });
|
|
24959
|
+
child.unref();
|
|
24960
|
+
await new Promise((resolve28) => setTimeout(resolve28, 3e3));
|
|
24961
|
+
try {
|
|
24962
|
+
models = await fetchOllamaModels(config.backendUrl);
|
|
24963
|
+
process.stdout.write(` ${c2.green("\u2714")} Ollama is running.
|
|
24017
24964
|
|
|
24018
24965
|
`);
|
|
24019
|
-
|
|
24020
|
-
|
|
24966
|
+
} catch {
|
|
24967
|
+
process.stdout.write(` ${c2.yellow("\u26A0")} Ollama started but not responding yet. It may need a moment.
|
|
24968
|
+
|
|
24969
|
+
`);
|
|
24970
|
+
}
|
|
24971
|
+
} catch {
|
|
24972
|
+
process.stdout.write(` ${c2.yellow("\u26A0")} Could not start Ollama. Try running ${c2.bold("ollama serve")} manually.
|
|
24973
|
+
|
|
24974
|
+
`);
|
|
24975
|
+
}
|
|
24976
|
+
} else {
|
|
24977
|
+
process.stdout.write(` ${c2.cyan("\u25CF")} Ollama not found. Installing automatically...
|
|
24978
|
+
`);
|
|
24979
|
+
const installed = await autoInstallOllama(rl);
|
|
24980
|
+
if (installed) {
|
|
24021
24981
|
process.stdout.write(`
|
|
24022
|
-
${c2.cyan("\u25CF")} Starting ollama serve
|
|
24982
|
+
${c2.cyan("\u25CF")} Starting ollama serve...
|
|
24023
24983
|
`);
|
|
24024
24984
|
try {
|
|
24025
24985
|
const child = spawn12("ollama", ["serve"], { stdio: "ignore", detached: true });
|
|
@@ -24031,45 +24991,16 @@ async function doSetup(config, rl) {
|
|
|
24031
24991
|
|
|
24032
24992
|
`);
|
|
24033
24993
|
} catch {
|
|
24034
|
-
process.stdout.write(` ${c2.yellow("\u26A0")} Ollama
|
|
24994
|
+
process.stdout.write(` ${c2.yellow("\u26A0")} Ollama installed but not responding yet. Try ${c2.bold("ollama serve")} manually.
|
|
24035
24995
|
|
|
24036
24996
|
`);
|
|
24037
24997
|
}
|
|
24038
24998
|
} catch {
|
|
24039
|
-
process.stdout.write(` ${c2.yellow("\u26A0")}
|
|
24999
|
+
process.stdout.write(` ${c2.yellow("\u26A0")} Ollama installed. Start it with: ${c2.bold("ollama serve")}
|
|
24040
25000
|
|
|
24041
25001
|
`);
|
|
24042
25002
|
}
|
|
24043
25003
|
}
|
|
24044
|
-
} else {
|
|
24045
|
-
const installOllama = await ask(rl, ` ${c2.bold("Install Ollama?")} (Y/n) `);
|
|
24046
|
-
if (installOllama.toLowerCase() !== "n") {
|
|
24047
|
-
const installed = await autoInstallOllama(rl);
|
|
24048
|
-
if (installed) {
|
|
24049
|
-
process.stdout.write(`
|
|
24050
|
-
${c2.cyan("\u25CF")} Starting ollama serve...
|
|
24051
|
-
`);
|
|
24052
|
-
try {
|
|
24053
|
-
const child = spawn12("ollama", ["serve"], { stdio: "ignore", detached: true });
|
|
24054
|
-
child.unref();
|
|
24055
|
-
await new Promise((resolve28) => setTimeout(resolve28, 3e3));
|
|
24056
|
-
try {
|
|
24057
|
-
models = await fetchOllamaModels(config.backendUrl);
|
|
24058
|
-
process.stdout.write(` ${c2.green("\u2714")} Ollama is running.
|
|
24059
|
-
|
|
24060
|
-
`);
|
|
24061
|
-
} catch {
|
|
24062
|
-
process.stdout.write(` ${c2.yellow("\u26A0")} Ollama installed but not responding yet. Try ${c2.bold("ollama serve")} manually.
|
|
24063
|
-
|
|
24064
|
-
`);
|
|
24065
|
-
}
|
|
24066
|
-
} catch {
|
|
24067
|
-
process.stdout.write(` ${c2.yellow("\u26A0")} Ollama installed. Start it with: ${c2.bold("ollama serve")}
|
|
24068
|
-
|
|
24069
|
-
`);
|
|
24070
|
-
}
|
|
24071
|
-
}
|
|
24072
|
-
}
|
|
24073
25004
|
if (models.length === 0) {
|
|
24074
25005
|
const useCustom = await ask(rl, ` ${c2.bold("Use a custom inference endpoint instead?")} (y/n) `);
|
|
24075
25006
|
if (useCustom.toLowerCase() === "y" || useCustom.toLowerCase() === "yes") {
|
|
@@ -24207,10 +25138,10 @@ async function doSetup(config, rl) {
|
|
|
24207
25138
|
`PARAMETER num_predict ${numPredict}`,
|
|
24208
25139
|
`PARAMETER stop "<|endoftext|>"`
|
|
24209
25140
|
].join("\n");
|
|
24210
|
-
const modelDir2 =
|
|
24211
|
-
|
|
24212
|
-
const modelfilePath =
|
|
24213
|
-
|
|
25141
|
+
const modelDir2 = join32(homedir10(), ".open-agents", "models");
|
|
25142
|
+
mkdirSync9(modelDir2, { recursive: true });
|
|
25143
|
+
const modelfilePath = join32(modelDir2, `Modelfile.${customName}`);
|
|
25144
|
+
writeFileSync9(modelfilePath, modelfileContent + "\n", "utf8");
|
|
24214
25145
|
process.stdout.write(` ${c2.dim("Creating model...")} `);
|
|
24215
25146
|
execSync20(`ollama create ${customName} -f ${modelfilePath}`, {
|
|
24216
25147
|
stdio: "pipe",
|
|
@@ -24255,7 +25186,7 @@ async function isModelAvailable(config) {
|
|
|
24255
25186
|
}
|
|
24256
25187
|
function isFirstRun() {
|
|
24257
25188
|
try {
|
|
24258
|
-
return !
|
|
25189
|
+
return !existsSync23(join32(homedir10(), ".open-agents", "config.json"));
|
|
24259
25190
|
} catch {
|
|
24260
25191
|
return true;
|
|
24261
25192
|
}
|
|
@@ -24292,7 +25223,7 @@ function detectPkgManager() {
|
|
|
24292
25223
|
return null;
|
|
24293
25224
|
}
|
|
24294
25225
|
function getVenvDir() {
|
|
24295
|
-
return
|
|
25226
|
+
return join32(homedir10(), ".open-agents", "venv");
|
|
24296
25227
|
}
|
|
24297
25228
|
function hasVenvModule() {
|
|
24298
25229
|
try {
|
|
@@ -24304,8 +25235,8 @@ function hasVenvModule() {
|
|
|
24304
25235
|
}
|
|
24305
25236
|
function ensureVenv(log) {
|
|
24306
25237
|
const venvDir = getVenvDir();
|
|
24307
|
-
const venvPip =
|
|
24308
|
-
if (
|
|
25238
|
+
const venvPip = join32(venvDir, "bin", "pip");
|
|
25239
|
+
if (existsSync23(venvPip))
|
|
24309
25240
|
return venvDir;
|
|
24310
25241
|
log("Creating Python venv for vision deps...");
|
|
24311
25242
|
if (!hasCmd("python3")) {
|
|
@@ -24317,9 +25248,9 @@ function ensureVenv(log) {
|
|
|
24317
25248
|
return null;
|
|
24318
25249
|
}
|
|
24319
25250
|
try {
|
|
24320
|
-
|
|
25251
|
+
mkdirSync9(join32(homedir10(), ".open-agents"), { recursive: true });
|
|
24321
25252
|
execSync20(`python3 -m venv "${venvDir}"`, { stdio: "pipe", timeout: 3e4 });
|
|
24322
|
-
execSync20(`"${
|
|
25253
|
+
execSync20(`"${join32(venvDir, "bin", "pip")}" install --upgrade pip`, {
|
|
24323
25254
|
stdio: "pipe",
|
|
24324
25255
|
timeout: 6e4
|
|
24325
25256
|
});
|
|
@@ -24528,15 +25459,15 @@ async function ensureVisionDeps(onInfo, getSudoPassword) {
|
|
|
24528
25459
|
}
|
|
24529
25460
|
}
|
|
24530
25461
|
const venvDir = getVenvDir();
|
|
24531
|
-
const venvBin =
|
|
24532
|
-
const venvMoondream =
|
|
25462
|
+
const venvBin = join32(venvDir, "bin");
|
|
25463
|
+
const venvMoondream = join32(venvBin, "moondream-station");
|
|
24533
25464
|
const venv = ensureVenv(log);
|
|
24534
|
-
if (venv && !hasCmd("moondream-station") && !
|
|
24535
|
-
const venvPip =
|
|
25465
|
+
if (venv && !hasCmd("moondream-station") && !existsSync23(venvMoondream)) {
|
|
25466
|
+
const venvPip = join32(venvBin, "pip");
|
|
24536
25467
|
log("Installing moondream-station in ~/.open-agents/venv...");
|
|
24537
25468
|
try {
|
|
24538
25469
|
execSync20(`"${venvPip}" install moondream-station`, { stdio: "pipe", timeout: 3e5 });
|
|
24539
|
-
if (
|
|
25470
|
+
if (existsSync23(venvMoondream)) {
|
|
24540
25471
|
log("moondream-station installed successfully.");
|
|
24541
25472
|
} else {
|
|
24542
25473
|
try {
|
|
@@ -24553,8 +25484,8 @@ async function ensureVisionDeps(onInfo, getSudoPassword) {
|
|
|
24553
25484
|
}
|
|
24554
25485
|
}
|
|
24555
25486
|
if (venv) {
|
|
24556
|
-
const venvPython =
|
|
24557
|
-
const venvPip2 =
|
|
25487
|
+
const venvPython = join32(venvBin, "python");
|
|
25488
|
+
const venvPip2 = join32(venvBin, "pip");
|
|
24558
25489
|
let ocrStackInstalled = false;
|
|
24559
25490
|
try {
|
|
24560
25491
|
execSync20(`"${venvPython}" -c "import cv2, pytesseract, numpy, PIL"`, { stdio: "pipe", timeout: 1e4 });
|
|
@@ -24663,10 +25594,10 @@ function createExpandedVariant(baseModel, specs, sizeGB) {
|
|
|
24663
25594
|
`PARAMETER num_predict ${numPredict}`,
|
|
24664
25595
|
`PARAMETER stop "<|endoftext|>"`
|
|
24665
25596
|
].join("\n");
|
|
24666
|
-
const modelDir2 =
|
|
24667
|
-
|
|
24668
|
-
const modelfilePath =
|
|
24669
|
-
|
|
25597
|
+
const modelDir2 = join32(homedir10(), ".open-agents", "models");
|
|
25598
|
+
mkdirSync9(modelDir2, { recursive: true });
|
|
25599
|
+
const modelfilePath = join32(modelDir2, `Modelfile.${customName}`);
|
|
25600
|
+
writeFileSync9(modelfilePath, modelfileContent + "\n", "utf8");
|
|
24670
25601
|
execSync20(`ollama create ${customName} -f ${modelfilePath}`, {
|
|
24671
25602
|
stdio: "pipe",
|
|
24672
25603
|
timeout: 12e4
|
|
@@ -25229,6 +26160,130 @@ async function handleSlashCommand(input, ctx) {
|
|
|
25229
26160
|
}
|
|
25230
26161
|
return "handled";
|
|
25231
26162
|
}
|
|
26163
|
+
case "p2p": {
|
|
26164
|
+
if (!ctx.p2pStart) {
|
|
26165
|
+
renderWarning("P2P mesh not available in this context.");
|
|
26166
|
+
return "handled";
|
|
26167
|
+
}
|
|
26168
|
+
const p2pSub = arg?.split(/\s+/)[0] ?? "";
|
|
26169
|
+
const p2pRest = arg?.slice(p2pSub.length).trim() ?? "";
|
|
26170
|
+
if (p2pSub === "stop" || p2pSub === "off") {
|
|
26171
|
+
if (ctx.isP2PActive?.()) {
|
|
26172
|
+
await ctx.p2pStop?.();
|
|
26173
|
+
renderInfo("P2P mesh stopped.");
|
|
26174
|
+
} else {
|
|
26175
|
+
renderWarning("P2P mesh not running.");
|
|
26176
|
+
}
|
|
26177
|
+
return "handled";
|
|
26178
|
+
}
|
|
26179
|
+
if (p2pSub === "status" || !p2pSub && ctx.isP2PActive?.()) {
|
|
26180
|
+
if (ctx.isP2PActive?.()) {
|
|
26181
|
+
process.stdout.write("\n" + (ctx.p2pStatus?.() ?? "") + "\n\n");
|
|
26182
|
+
} else {
|
|
26183
|
+
renderWarning("P2P mesh not running. Start with: /p2p start");
|
|
26184
|
+
}
|
|
26185
|
+
return "handled";
|
|
26186
|
+
}
|
|
26187
|
+
if (p2pSub === "connect") {
|
|
26188
|
+
if (!ctx.isP2PActive?.()) {
|
|
26189
|
+
renderWarning("Start mesh first: /p2p start");
|
|
26190
|
+
return "handled";
|
|
26191
|
+
}
|
|
26192
|
+
if (!p2pRest) {
|
|
26193
|
+
renderInfo("Usage: /p2p connect <endpoint> [--trust verified|tee|public]");
|
|
26194
|
+
return "handled";
|
|
26195
|
+
}
|
|
26196
|
+
const parts = p2pRest.split(/\s+/);
|
|
26197
|
+
const endpoint = parts[0];
|
|
26198
|
+
const trustIdx = parts.indexOf("--trust");
|
|
26199
|
+
const trust = trustIdx !== -1 ? parts[trustIdx + 1] : void 0;
|
|
26200
|
+
try {
|
|
26201
|
+
const peerId = await ctx.p2pConnect(endpoint, trust);
|
|
26202
|
+
if (peerId) {
|
|
26203
|
+
renderInfo(`Connected to peer: ${c2.bold(peerId)}`);
|
|
26204
|
+
} else {
|
|
26205
|
+
renderError("Failed to connect to peer.");
|
|
26206
|
+
}
|
|
26207
|
+
} catch (err) {
|
|
26208
|
+
renderError(`Connection failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
26209
|
+
}
|
|
26210
|
+
return "handled";
|
|
26211
|
+
}
|
|
26212
|
+
if (p2pSub === "start" || !p2pSub) {
|
|
26213
|
+
if (ctx.isP2PActive?.()) {
|
|
26214
|
+
process.stdout.write("\n" + (ctx.p2pStatus?.() ?? "") + "\n\n");
|
|
26215
|
+
return "handled";
|
|
26216
|
+
}
|
|
26217
|
+
try {
|
|
26218
|
+
const bootstrapPeers = p2pRest ? p2pRest.split(/[,\s]+/).filter(Boolean) : void 0;
|
|
26219
|
+
const result = await ctx.p2pStart(bootstrapPeers);
|
|
26220
|
+
renderInfo(`P2P mesh started. Peer ID: ${c2.bold(result.peerId)} (port ${result.port})`);
|
|
26221
|
+
renderInfo("Connect peers with: /p2p connect <endpoint>");
|
|
26222
|
+
} catch (err) {
|
|
26223
|
+
renderError(`P2P start failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
26224
|
+
}
|
|
26225
|
+
return "handled";
|
|
26226
|
+
}
|
|
26227
|
+
renderInfo("Usage: /p2p start [bootstrap-peers...] | /p2p connect <endpoint> | /p2p status | /p2p stop");
|
|
26228
|
+
return "handled";
|
|
26229
|
+
}
|
|
26230
|
+
case "secrets":
|
|
26231
|
+
case "secret":
|
|
26232
|
+
case "vault": {
|
|
26233
|
+
if (!ctx.secretSet) {
|
|
26234
|
+
renderWarning("Secret vault not available in this context.");
|
|
26235
|
+
return "handled";
|
|
26236
|
+
}
|
|
26237
|
+
const secSub = arg?.split(/\s+/)[0] ?? "";
|
|
26238
|
+
const secRest = arg?.slice(secSub.length).trim() ?? "";
|
|
26239
|
+
if (secSub === "set") {
|
|
26240
|
+
const parts = secRest.split(/\s+/);
|
|
26241
|
+
const name = parts[0];
|
|
26242
|
+
const value = parts.slice(1).join(" ");
|
|
26243
|
+
if (!name || !value) {
|
|
26244
|
+
renderInfo("Usage: /secrets set <NAME> <value>");
|
|
26245
|
+
return "handled";
|
|
26246
|
+
}
|
|
26247
|
+
ctx.secretSet(name, value);
|
|
26248
|
+
renderInfo(`Secret "${name}" registered.`);
|
|
26249
|
+
return "handled";
|
|
26250
|
+
}
|
|
26251
|
+
if (secSub === "delete" || secSub === "rm") {
|
|
26252
|
+
if (!secRest) {
|
|
26253
|
+
renderInfo("Usage: /secrets delete <NAME>");
|
|
26254
|
+
return "handled";
|
|
26255
|
+
}
|
|
26256
|
+
const deleted = ctx.secretDelete(secRest);
|
|
26257
|
+
if (deleted)
|
|
26258
|
+
renderInfo(`Secret "${secRest}" removed.`);
|
|
26259
|
+
else
|
|
26260
|
+
renderWarning(`Secret "${secRest}" not found.`);
|
|
26261
|
+
return "handled";
|
|
26262
|
+
}
|
|
26263
|
+
if (secSub === "import-env") {
|
|
26264
|
+
const count = ctx.secretImportEnv();
|
|
26265
|
+
renderInfo(`Imported ${count} secrets from environment variables.`);
|
|
26266
|
+
return "handled";
|
|
26267
|
+
}
|
|
26268
|
+
const secrets = ctx.secretList();
|
|
26269
|
+
if (secrets.length === 0) {
|
|
26270
|
+
renderInfo("No secrets registered. Use: /secrets set <NAME> <value>");
|
|
26271
|
+
} else {
|
|
26272
|
+
process.stdout.write(`
|
|
26273
|
+
${c2.bold("Registered Secrets")} (${secrets.length})
|
|
26274
|
+
|
|
26275
|
+
`);
|
|
26276
|
+
for (const s of secrets) {
|
|
26277
|
+
process.stdout.write(` ${c2.cyan(s.name.padEnd(24))} min trust: ${c2.bold(s.minTrust)}
|
|
26278
|
+
`);
|
|
26279
|
+
}
|
|
26280
|
+
process.stdout.write(`
|
|
26281
|
+
${c2.dim("Secrets are redacted from prompts sent to peers below their trust tier.")}
|
|
26282
|
+
|
|
26283
|
+
`);
|
|
26284
|
+
}
|
|
26285
|
+
return "handled";
|
|
26286
|
+
}
|
|
25232
26287
|
case "bruteforce":
|
|
25233
26288
|
case "brute": {
|
|
25234
26289
|
const isOn = ctx.bruteForceToggle();
|
|
@@ -25679,17 +26734,17 @@ async function handleUpdate(subcommand, ctx) {
|
|
|
25679
26734
|
try {
|
|
25680
26735
|
const { createRequire: createRequire4 } = await import("node:module");
|
|
25681
26736
|
const { fileURLToPath: fileURLToPath11 } = await import("node:url");
|
|
25682
|
-
const { dirname:
|
|
25683
|
-
const { existsSync:
|
|
26737
|
+
const { dirname: dirname15, join: join46 } = await import("node:path");
|
|
26738
|
+
const { existsSync: existsSync33 } = await import("node:fs");
|
|
25684
26739
|
const req = createRequire4(import.meta.url);
|
|
25685
|
-
const thisDir =
|
|
26740
|
+
const thisDir = dirname15(fileURLToPath11(import.meta.url));
|
|
25686
26741
|
const candidates = [
|
|
25687
|
-
|
|
25688
|
-
|
|
25689
|
-
|
|
26742
|
+
join46(thisDir, "..", "package.json"),
|
|
26743
|
+
join46(thisDir, "..", "..", "package.json"),
|
|
26744
|
+
join46(thisDir, "..", "..", "..", "package.json")
|
|
25690
26745
|
];
|
|
25691
26746
|
for (const pkgPath of candidates) {
|
|
25692
|
-
if (
|
|
26747
|
+
if (existsSync33(pkgPath)) {
|
|
25693
26748
|
const pkg = req(pkgPath);
|
|
25694
26749
|
if (pkg.name === "open-agents-ai" || pkg.name === "@open-agents/cli") {
|
|
25695
26750
|
currentVersion = pkg.version ?? "0.0.0";
|
|
@@ -25892,8 +26947,8 @@ var init_commands = __esm({
|
|
|
25892
26947
|
});
|
|
25893
26948
|
|
|
25894
26949
|
// packages/cli/dist/tui/project-context.js
|
|
25895
|
-
import { existsSync as
|
|
25896
|
-
import { join as
|
|
26950
|
+
import { existsSync as existsSync24, readFileSync as readFileSync17, readdirSync as readdirSync8 } from "node:fs";
|
|
26951
|
+
import { join as join33, basename as basename10 } from "node:path";
|
|
25897
26952
|
import { execSync as execSync21 } from "node:child_process";
|
|
25898
26953
|
import { homedir as homedir11, platform as platform2, release } from "node:os";
|
|
25899
26954
|
function getModelTier(modelName) {
|
|
@@ -25928,10 +26983,10 @@ function loadProjectMap(repoRoot) {
|
|
|
25928
26983
|
if (!hasOaDirectory(repoRoot)) {
|
|
25929
26984
|
initOaDirectory(repoRoot);
|
|
25930
26985
|
}
|
|
25931
|
-
const mapPath =
|
|
25932
|
-
if (
|
|
26986
|
+
const mapPath = join33(repoRoot, OA_DIR, "context", "project-map.md");
|
|
26987
|
+
if (existsSync24(mapPath)) {
|
|
25933
26988
|
try {
|
|
25934
|
-
const content =
|
|
26989
|
+
const content = readFileSync17(mapPath, "utf-8");
|
|
25935
26990
|
return content;
|
|
25936
26991
|
} catch {
|
|
25937
26992
|
}
|
|
@@ -25972,31 +27027,31 @@ ${log}`);
|
|
|
25972
27027
|
}
|
|
25973
27028
|
function loadMemoryContext(repoRoot) {
|
|
25974
27029
|
const sections = [];
|
|
25975
|
-
const oaMemDir =
|
|
27030
|
+
const oaMemDir = join33(repoRoot, OA_DIR, "memory");
|
|
25976
27031
|
const oaEntries = loadMemoryDir(oaMemDir, "project");
|
|
25977
27032
|
if (oaEntries)
|
|
25978
27033
|
sections.push(oaEntries);
|
|
25979
|
-
const legacyMemDir =
|
|
25980
|
-
if (legacyMemDir !== oaMemDir &&
|
|
27034
|
+
const legacyMemDir = join33(repoRoot, ".open-agents", "memory");
|
|
27035
|
+
if (legacyMemDir !== oaMemDir && existsSync24(legacyMemDir)) {
|
|
25981
27036
|
const legacyEntries = loadMemoryDir(legacyMemDir, "project/legacy");
|
|
25982
27037
|
if (legacyEntries)
|
|
25983
27038
|
sections.push(legacyEntries);
|
|
25984
27039
|
}
|
|
25985
|
-
const globalMemDir =
|
|
27040
|
+
const globalMemDir = join33(homedir11(), ".open-agents", "memory");
|
|
25986
27041
|
const globalEntries = loadMemoryDir(globalMemDir, "global");
|
|
25987
27042
|
if (globalEntries)
|
|
25988
27043
|
sections.push(globalEntries);
|
|
25989
27044
|
return sections.join("\n\n");
|
|
25990
27045
|
}
|
|
25991
27046
|
function loadMemoryDir(memDir, scope) {
|
|
25992
|
-
if (!
|
|
27047
|
+
if (!existsSync24(memDir))
|
|
25993
27048
|
return "";
|
|
25994
27049
|
const lines = [];
|
|
25995
27050
|
try {
|
|
25996
27051
|
const files = readdirSync8(memDir).filter((f) => f.endsWith(".json"));
|
|
25997
27052
|
for (const file of files.slice(0, 10)) {
|
|
25998
27053
|
try {
|
|
25999
|
-
const raw =
|
|
27054
|
+
const raw = readFileSync17(join33(memDir, file), "utf-8");
|
|
26000
27055
|
const entries = JSON.parse(raw);
|
|
26001
27056
|
const topic = basename10(file, ".json");
|
|
26002
27057
|
const keys = Object.keys(entries);
|
|
@@ -27023,22 +28078,22 @@ var init_carousel = __esm({
|
|
|
27023
28078
|
});
|
|
27024
28079
|
|
|
27025
28080
|
// packages/cli/dist/tui/carousel-descriptors.js
|
|
27026
|
-
import { existsSync as
|
|
27027
|
-
import { join as
|
|
28081
|
+
import { existsSync as existsSync25, readFileSync as readFileSync18, writeFileSync as writeFileSync10, mkdirSync as mkdirSync10, readdirSync as readdirSync9 } from "node:fs";
|
|
28082
|
+
import { join as join34, basename as basename11 } from "node:path";
|
|
27028
28083
|
function loadToolProfile(repoRoot) {
|
|
27029
|
-
const filePath =
|
|
28084
|
+
const filePath = join34(repoRoot, OA_DIR, "context", TOOL_PROFILE_FILE);
|
|
27030
28085
|
try {
|
|
27031
|
-
if (!
|
|
28086
|
+
if (!existsSync25(filePath))
|
|
27032
28087
|
return null;
|
|
27033
|
-
return JSON.parse(
|
|
28088
|
+
return JSON.parse(readFileSync18(filePath, "utf-8"));
|
|
27034
28089
|
} catch {
|
|
27035
28090
|
return null;
|
|
27036
28091
|
}
|
|
27037
28092
|
}
|
|
27038
28093
|
function saveToolProfile(repoRoot, profile) {
|
|
27039
|
-
const contextDir =
|
|
27040
|
-
|
|
27041
|
-
|
|
28094
|
+
const contextDir = join34(repoRoot, OA_DIR, "context");
|
|
28095
|
+
mkdirSync10(contextDir, { recursive: true });
|
|
28096
|
+
writeFileSync10(join34(contextDir, TOOL_PROFILE_FILE), JSON.stringify(profile, null, 2), "utf-8");
|
|
27042
28097
|
}
|
|
27043
28098
|
function categorizeToolCall(toolName) {
|
|
27044
28099
|
for (const cat of TOOL_CATEGORIES) {
|
|
@@ -27096,25 +28151,25 @@ function weightedColor(profile) {
|
|
|
27096
28151
|
return selectedCat.colors[Math.floor(Math.random() * selectedCat.colors.length)];
|
|
27097
28152
|
}
|
|
27098
28153
|
function loadCachedDescriptors(repoRoot) {
|
|
27099
|
-
const filePath =
|
|
28154
|
+
const filePath = join34(repoRoot, OA_DIR, "context", DESCRIPTOR_FILE);
|
|
27100
28155
|
try {
|
|
27101
|
-
if (!
|
|
28156
|
+
if (!existsSync25(filePath))
|
|
27102
28157
|
return null;
|
|
27103
|
-
const cached = JSON.parse(
|
|
28158
|
+
const cached = JSON.parse(readFileSync18(filePath, "utf-8"));
|
|
27104
28159
|
return cached.phrases.length > 0 ? cached.phrases : null;
|
|
27105
28160
|
} catch {
|
|
27106
28161
|
return null;
|
|
27107
28162
|
}
|
|
27108
28163
|
}
|
|
27109
28164
|
function saveCachedDescriptors(repoRoot, phrases, sourceHash) {
|
|
27110
|
-
const contextDir =
|
|
27111
|
-
|
|
28165
|
+
const contextDir = join34(repoRoot, OA_DIR, "context");
|
|
28166
|
+
mkdirSync10(contextDir, { recursive: true });
|
|
27112
28167
|
const cached = {
|
|
27113
28168
|
phrases,
|
|
27114
28169
|
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
27115
28170
|
sourceHash
|
|
27116
28171
|
};
|
|
27117
|
-
|
|
28172
|
+
writeFileSync10(join34(contextDir, DESCRIPTOR_FILE), JSON.stringify(cached, null, 2), "utf-8");
|
|
27118
28173
|
}
|
|
27119
28174
|
function generateDescriptors(repoRoot) {
|
|
27120
28175
|
const profile = loadToolProfile(repoRoot);
|
|
@@ -27162,11 +28217,11 @@ function generateDescriptors(repoRoot) {
|
|
|
27162
28217
|
return phrases;
|
|
27163
28218
|
}
|
|
27164
28219
|
function extractFromPackageJson(repoRoot, tags) {
|
|
27165
|
-
const pkgPath =
|
|
28220
|
+
const pkgPath = join34(repoRoot, "package.json");
|
|
27166
28221
|
try {
|
|
27167
|
-
if (!
|
|
28222
|
+
if (!existsSync25(pkgPath))
|
|
27168
28223
|
return;
|
|
27169
|
-
const pkg = JSON.parse(
|
|
28224
|
+
const pkg = JSON.parse(readFileSync18(pkgPath, "utf-8"));
|
|
27170
28225
|
if (pkg.name && typeof pkg.name === "string") {
|
|
27171
28226
|
const parts = pkg.name.replace(/^@/, "").split("/");
|
|
27172
28227
|
for (const p of parts)
|
|
@@ -27210,7 +28265,7 @@ function extractFromManifests(repoRoot, tags) {
|
|
|
27210
28265
|
{ file: ".github/workflows", tag: "ci/cd" }
|
|
27211
28266
|
];
|
|
27212
28267
|
for (const check of manifestChecks) {
|
|
27213
|
-
if (
|
|
28268
|
+
if (existsSync25(join34(repoRoot, check.file))) {
|
|
27214
28269
|
tags.push(check.tag);
|
|
27215
28270
|
}
|
|
27216
28271
|
}
|
|
@@ -27232,16 +28287,16 @@ function extractFromSessions(repoRoot, tags) {
|
|
|
27232
28287
|
}
|
|
27233
28288
|
}
|
|
27234
28289
|
function extractFromMemory(repoRoot, tags) {
|
|
27235
|
-
const memoryDir =
|
|
28290
|
+
const memoryDir = join34(repoRoot, OA_DIR, "memory");
|
|
27236
28291
|
try {
|
|
27237
|
-
if (!
|
|
28292
|
+
if (!existsSync25(memoryDir))
|
|
27238
28293
|
return;
|
|
27239
28294
|
const files = readdirSync9(memoryDir).filter((f) => f.endsWith(".json"));
|
|
27240
28295
|
for (const file of files) {
|
|
27241
28296
|
const topic = file.replace(/\.json$/, "").replace(/[-_]/g, " ");
|
|
27242
28297
|
tags.push(topic);
|
|
27243
28298
|
try {
|
|
27244
|
-
const data = JSON.parse(
|
|
28299
|
+
const data = JSON.parse(readFileSync18(join34(memoryDir, file), "utf-8"));
|
|
27245
28300
|
if (data && typeof data === "object") {
|
|
27246
28301
|
const keys = Object.keys(data).slice(0, 3);
|
|
27247
28302
|
for (const key of keys) {
|
|
@@ -27376,25 +28431,25 @@ var init_carousel_descriptors = __esm({
|
|
|
27376
28431
|
});
|
|
27377
28432
|
|
|
27378
28433
|
// packages/cli/dist/tui/voice.js
|
|
27379
|
-
import { existsSync as
|
|
27380
|
-
import { join as
|
|
28434
|
+
import { existsSync as existsSync26, mkdirSync as mkdirSync11, writeFileSync as writeFileSync11, readFileSync as readFileSync19, unlinkSync as unlinkSync4 } from "node:fs";
|
|
28435
|
+
import { join as join35 } from "node:path";
|
|
27381
28436
|
import { homedir as homedir12, tmpdir as tmpdir6, platform as platform3 } from "node:os";
|
|
27382
28437
|
import { execSync as execSync22, spawn as nodeSpawn } from "node:child_process";
|
|
27383
28438
|
import { createRequire } from "node:module";
|
|
27384
28439
|
function voiceDir() {
|
|
27385
|
-
return
|
|
28440
|
+
return join35(homedir12(), ".open-agents", "voice");
|
|
27386
28441
|
}
|
|
27387
28442
|
function modelsDir() {
|
|
27388
|
-
return
|
|
28443
|
+
return join35(voiceDir(), "models");
|
|
27389
28444
|
}
|
|
27390
28445
|
function modelDir(id) {
|
|
27391
|
-
return
|
|
28446
|
+
return join35(modelsDir(), id);
|
|
27392
28447
|
}
|
|
27393
28448
|
function modelOnnxPath(id) {
|
|
27394
|
-
return
|
|
28449
|
+
return join35(modelDir(id), "model.onnx");
|
|
27395
28450
|
}
|
|
27396
28451
|
function modelConfigPath(id) {
|
|
27397
|
-
return
|
|
28452
|
+
return join35(modelDir(id), "config.json");
|
|
27398
28453
|
}
|
|
27399
28454
|
function resetNarrationContext() {
|
|
27400
28455
|
narration.toolCount = 0;
|
|
@@ -28072,7 +29127,7 @@ var init_voice = __esm({
|
|
|
28072
29127
|
}
|
|
28073
29128
|
this.onPCMOutput(Buffer.from(int16.buffer), this.config.audio.sample_rate);
|
|
28074
29129
|
}
|
|
28075
|
-
const wavPath =
|
|
29130
|
+
const wavPath = join35(tmpdir6(), `oa-voice-${Date.now()}.wav`);
|
|
28076
29131
|
this.writeWav(audioData, this.config.audio.sample_rate, wavPath);
|
|
28077
29132
|
await this.playWav(wavPath);
|
|
28078
29133
|
try {
|
|
@@ -28156,7 +29211,7 @@ var init_voice = __esm({
|
|
|
28156
29211
|
return buffer;
|
|
28157
29212
|
}
|
|
28158
29213
|
writeWav(samples, sampleRate, path) {
|
|
28159
|
-
|
|
29214
|
+
writeFileSync11(path, this.buildWavBuffer(samples, sampleRate));
|
|
28160
29215
|
}
|
|
28161
29216
|
// -------------------------------------------------------------------------
|
|
28162
29217
|
// Audio playback (system default speakers)
|
|
@@ -28230,30 +29285,30 @@ var init_voice = __esm({
|
|
|
28230
29285
|
return;
|
|
28231
29286
|
const arch = process.arch;
|
|
28232
29287
|
const isArmLinux = (arch === "arm64" || arch === "arm") && process.platform === "linux";
|
|
28233
|
-
|
|
28234
|
-
const pkgPath =
|
|
29288
|
+
mkdirSync11(voiceDir(), { recursive: true });
|
|
29289
|
+
const pkgPath = join35(voiceDir(), "package.json");
|
|
28235
29290
|
const expectedDeps = {
|
|
28236
29291
|
"onnxruntime-node": "^1.21.0",
|
|
28237
29292
|
"phonemizer": "^1.2.1"
|
|
28238
29293
|
};
|
|
28239
|
-
if (
|
|
29294
|
+
if (existsSync26(pkgPath)) {
|
|
28240
29295
|
try {
|
|
28241
|
-
const existing = JSON.parse(
|
|
29296
|
+
const existing = JSON.parse(readFileSync19(pkgPath, "utf8"));
|
|
28242
29297
|
if (!existing.dependencies?.["phonemizer"]) {
|
|
28243
29298
|
existing.dependencies = { ...existing.dependencies, ...expectedDeps };
|
|
28244
|
-
|
|
29299
|
+
writeFileSync11(pkgPath, JSON.stringify(existing, null, 2));
|
|
28245
29300
|
}
|
|
28246
29301
|
} catch {
|
|
28247
29302
|
}
|
|
28248
29303
|
}
|
|
28249
|
-
if (!
|
|
28250
|
-
|
|
29304
|
+
if (!existsSync26(pkgPath)) {
|
|
29305
|
+
writeFileSync11(pkgPath, JSON.stringify({
|
|
28251
29306
|
name: "open-agents-voice",
|
|
28252
29307
|
private: true,
|
|
28253
29308
|
dependencies: expectedDeps
|
|
28254
29309
|
}, null, 2));
|
|
28255
29310
|
}
|
|
28256
|
-
const voiceRequire = createRequire(
|
|
29311
|
+
const voiceRequire = createRequire(join35(voiceDir(), "index.js"));
|
|
28257
29312
|
try {
|
|
28258
29313
|
this.ort = voiceRequire("onnxruntime-node");
|
|
28259
29314
|
} catch {
|
|
@@ -28307,18 +29362,18 @@ Error: ${err instanceof Error ? err.message : String(err)}`);
|
|
|
28307
29362
|
const dir = modelDir(id);
|
|
28308
29363
|
const onnxPath = modelOnnxPath(id);
|
|
28309
29364
|
const configPath = modelConfigPath(id);
|
|
28310
|
-
if (
|
|
29365
|
+
if (existsSync26(onnxPath) && existsSync26(configPath))
|
|
28311
29366
|
return;
|
|
28312
|
-
|
|
28313
|
-
if (!
|
|
29367
|
+
mkdirSync11(dir, { recursive: true });
|
|
29368
|
+
if (!existsSync26(configPath)) {
|
|
28314
29369
|
renderInfo(`Downloading ${model.label} voice config...`);
|
|
28315
29370
|
const configResp = await fetch(model.configUrl);
|
|
28316
29371
|
if (!configResp.ok)
|
|
28317
29372
|
throw new Error(`Failed to download config: HTTP ${configResp.status}`);
|
|
28318
29373
|
const configText = await configResp.text();
|
|
28319
|
-
|
|
29374
|
+
writeFileSync11(configPath, configText);
|
|
28320
29375
|
}
|
|
28321
|
-
if (!
|
|
29376
|
+
if (!existsSync26(onnxPath)) {
|
|
28322
29377
|
renderInfo(`Downloading ${model.label} voice model (this may take a minute)...`);
|
|
28323
29378
|
const onnxResp = await fetch(model.onnxUrl);
|
|
28324
29379
|
if (!onnxResp.ok)
|
|
@@ -28342,7 +29397,7 @@ Error: ${err instanceof Error ? err.message : String(err)}`);
|
|
|
28342
29397
|
}
|
|
28343
29398
|
process.stdout.write("\r" + " ".repeat(60) + "\r");
|
|
28344
29399
|
const fullBuffer = Buffer.concat(chunks);
|
|
28345
|
-
|
|
29400
|
+
writeFileSync11(onnxPath, fullBuffer);
|
|
28346
29401
|
renderInfo(`${model.label} model downloaded (${formatBytes2(fullBuffer.length)}).`);
|
|
28347
29402
|
}
|
|
28348
29403
|
}
|
|
@@ -28354,10 +29409,10 @@ Error: ${err instanceof Error ? err.message : String(err)}`);
|
|
|
28354
29409
|
throw new Error("ONNX runtime not loaded");
|
|
28355
29410
|
const onnxPath = modelOnnxPath(this.modelId);
|
|
28356
29411
|
const configPath = modelConfigPath(this.modelId);
|
|
28357
|
-
if (!
|
|
29412
|
+
if (!existsSync26(onnxPath) || !existsSync26(configPath)) {
|
|
28358
29413
|
throw new Error(`Model files not found for ${this.modelId}`);
|
|
28359
29414
|
}
|
|
28360
|
-
this.config = JSON.parse(
|
|
29415
|
+
this.config = JSON.parse(readFileSync19(configPath, "utf8"));
|
|
28361
29416
|
renderInfo("Loading voice model...");
|
|
28362
29417
|
this.session = await this.ort.InferenceSession.create(onnxPath, {
|
|
28363
29418
|
executionProviders: ["cpu"],
|
|
@@ -29220,13 +30275,13 @@ var init_stream_renderer = __esm({
|
|
|
29220
30275
|
});
|
|
29221
30276
|
|
|
29222
30277
|
// packages/cli/dist/tui/edit-history.js
|
|
29223
|
-
import { appendFileSync as appendFileSync2, mkdirSync as
|
|
29224
|
-
import { join as
|
|
30278
|
+
import { appendFileSync as appendFileSync2, mkdirSync as mkdirSync12 } from "node:fs";
|
|
30279
|
+
import { join as join36 } from "node:path";
|
|
29225
30280
|
function createEditHistoryLogger(repoRoot, sessionId) {
|
|
29226
|
-
const historyDir =
|
|
29227
|
-
const logPath =
|
|
30281
|
+
const historyDir = join36(repoRoot, ".oa", "history");
|
|
30282
|
+
const logPath = join36(historyDir, "edits.jsonl");
|
|
29228
30283
|
try {
|
|
29229
|
-
|
|
30284
|
+
mkdirSync12(historyDir, { recursive: true });
|
|
29230
30285
|
} catch {
|
|
29231
30286
|
}
|
|
29232
30287
|
function logToolCall(toolName, toolArgs, success) {
|
|
@@ -29335,15 +30390,15 @@ var init_edit_history = __esm({
|
|
|
29335
30390
|
});
|
|
29336
30391
|
|
|
29337
30392
|
// packages/cli/dist/tui/dream-engine.js
|
|
29338
|
-
import { mkdirSync as
|
|
29339
|
-
import { join as
|
|
30393
|
+
import { mkdirSync as mkdirSync13, writeFileSync as writeFileSync12, readFileSync as readFileSync20, existsSync as existsSync27, cpSync, rmSync, readdirSync as readdirSync10 } from "node:fs";
|
|
30394
|
+
import { join as join37, basename as basename12 } from "node:path";
|
|
29340
30395
|
import { execSync as execSync23 } from "node:child_process";
|
|
29341
30396
|
function loadAutoresearchMemory(repoRoot) {
|
|
29342
|
-
const memoryPath =
|
|
29343
|
-
if (!
|
|
30397
|
+
const memoryPath = join37(repoRoot, ".oa", "memory", "autoresearch.json");
|
|
30398
|
+
if (!existsSync27(memoryPath))
|
|
29344
30399
|
return "";
|
|
29345
30400
|
try {
|
|
29346
|
-
const raw =
|
|
30401
|
+
const raw = readFileSync20(memoryPath, "utf-8");
|
|
29347
30402
|
const data = JSON.parse(raw);
|
|
29348
30403
|
const sections = [];
|
|
29349
30404
|
for (const key of AUTORESEARCH_MEMORY_KEYS) {
|
|
@@ -29563,14 +30618,14 @@ var init_dream_engine = __esm({
|
|
|
29563
30618
|
const content = String(args["content"] ?? "");
|
|
29564
30619
|
if (!rawPath)
|
|
29565
30620
|
return { success: false, output: "", error: "path is required", durationMs: Date.now() - start };
|
|
29566
|
-
const targetPath = rawPath.startsWith("/") || rawPath.startsWith(".oa/autoresearch") ?
|
|
30621
|
+
const targetPath = rawPath.startsWith("/") || rawPath.startsWith(".oa/autoresearch") ? join37(this.autoresearchDir, basename12(rawPath)) : join37(this.autoresearchDir, rawPath);
|
|
29567
30622
|
if (!targetPath.startsWith(this.autoresearchDir)) {
|
|
29568
30623
|
return { success: false, output: "", error: "Autoresearch mode: writes are confined to .oa/autoresearch/", durationMs: Date.now() - start };
|
|
29569
30624
|
}
|
|
29570
30625
|
try {
|
|
29571
|
-
const dir =
|
|
29572
|
-
|
|
29573
|
-
|
|
30626
|
+
const dir = join37(targetPath, "..");
|
|
30627
|
+
mkdirSync13(dir, { recursive: true });
|
|
30628
|
+
writeFileSync12(targetPath, content, "utf-8");
|
|
29574
30629
|
return { success: true, output: `Wrote ${content.length} bytes to ${rawPath}`, durationMs: Date.now() - start };
|
|
29575
30630
|
} catch (err) {
|
|
29576
30631
|
return { success: false, output: "", error: String(err), durationMs: Date.now() - start };
|
|
@@ -29598,20 +30653,20 @@ var init_dream_engine = __esm({
|
|
|
29598
30653
|
const rawPath = String(args["path"] ?? "");
|
|
29599
30654
|
const oldStr = String(args["old_string"] ?? "");
|
|
29600
30655
|
const newStr = String(args["new_string"] ?? "");
|
|
29601
|
-
const targetPath = rawPath.startsWith("/") || rawPath.startsWith(".oa/autoresearch") ?
|
|
30656
|
+
const targetPath = rawPath.startsWith("/") || rawPath.startsWith(".oa/autoresearch") ? join37(this.autoresearchDir, basename12(rawPath)) : join37(this.autoresearchDir, rawPath);
|
|
29602
30657
|
if (!targetPath.startsWith(this.autoresearchDir)) {
|
|
29603
30658
|
return { success: false, output: "", error: "Autoresearch mode: edits are confined to .oa/autoresearch/", durationMs: Date.now() - start };
|
|
29604
30659
|
}
|
|
29605
30660
|
try {
|
|
29606
|
-
if (!
|
|
30661
|
+
if (!existsSync27(targetPath)) {
|
|
29607
30662
|
return { success: false, output: "", error: `File not found: ${rawPath}`, durationMs: Date.now() - start };
|
|
29608
30663
|
}
|
|
29609
|
-
let content =
|
|
30664
|
+
let content = readFileSync20(targetPath, "utf-8");
|
|
29610
30665
|
if (!content.includes(oldStr)) {
|
|
29611
30666
|
return { success: false, output: "", error: "old_string not found in file", durationMs: Date.now() - start };
|
|
29612
30667
|
}
|
|
29613
30668
|
content = content.replace(oldStr, newStr);
|
|
29614
|
-
|
|
30669
|
+
writeFileSync12(targetPath, content, "utf-8");
|
|
29615
30670
|
return { success: true, output: `Edited ${rawPath}`, durationMs: Date.now() - start };
|
|
29616
30671
|
} catch (err) {
|
|
29617
30672
|
return { success: false, output: "", error: String(err), durationMs: Date.now() - start };
|
|
@@ -29652,14 +30707,14 @@ var init_dream_engine = __esm({
|
|
|
29652
30707
|
const content = String(args["content"] ?? "");
|
|
29653
30708
|
if (!rawPath)
|
|
29654
30709
|
return { success: false, output: "", error: "path is required", durationMs: Date.now() - start };
|
|
29655
|
-
const targetPath = rawPath.startsWith("/") || rawPath.startsWith(".oa/dreams") ?
|
|
30710
|
+
const targetPath = rawPath.startsWith("/") || rawPath.startsWith(".oa/dreams") ? join37(this.dreamsDir, basename12(rawPath)) : join37(this.dreamsDir, rawPath);
|
|
29656
30711
|
if (!targetPath.startsWith(this.dreamsDir)) {
|
|
29657
30712
|
return { success: false, output: "", error: "Dream mode: writes are confined to .oa/dreams/", durationMs: Date.now() - start };
|
|
29658
30713
|
}
|
|
29659
30714
|
try {
|
|
29660
|
-
const dir =
|
|
29661
|
-
|
|
29662
|
-
|
|
30715
|
+
const dir = join37(targetPath, "..");
|
|
30716
|
+
mkdirSync13(dir, { recursive: true });
|
|
30717
|
+
writeFileSync12(targetPath, content, "utf-8");
|
|
29663
30718
|
return { success: true, output: `Wrote ${content.length} bytes to ${rawPath}`, durationMs: Date.now() - start };
|
|
29664
30719
|
} catch (err) {
|
|
29665
30720
|
return { success: false, output: "", error: String(err), durationMs: Date.now() - start };
|
|
@@ -29687,20 +30742,20 @@ var init_dream_engine = __esm({
|
|
|
29687
30742
|
const rawPath = String(args["path"] ?? "");
|
|
29688
30743
|
const oldStr = String(args["old_string"] ?? "");
|
|
29689
30744
|
const newStr = String(args["new_string"] ?? "");
|
|
29690
|
-
const targetPath = rawPath.startsWith("/") || rawPath.startsWith(".oa/dreams") ?
|
|
30745
|
+
const targetPath = rawPath.startsWith("/") || rawPath.startsWith(".oa/dreams") ? join37(this.dreamsDir, basename12(rawPath)) : join37(this.dreamsDir, rawPath);
|
|
29691
30746
|
if (!targetPath.startsWith(this.dreamsDir)) {
|
|
29692
30747
|
return { success: false, output: "", error: "Dream mode: edits are confined to .oa/dreams/", durationMs: Date.now() - start };
|
|
29693
30748
|
}
|
|
29694
30749
|
try {
|
|
29695
|
-
if (!
|
|
30750
|
+
if (!existsSync27(targetPath)) {
|
|
29696
30751
|
return { success: false, output: "", error: `File not found: ${rawPath}`, durationMs: Date.now() - start };
|
|
29697
30752
|
}
|
|
29698
|
-
let content =
|
|
30753
|
+
let content = readFileSync20(targetPath, "utf-8");
|
|
29699
30754
|
if (!content.includes(oldStr)) {
|
|
29700
30755
|
return { success: false, output: "", error: "old_string not found in file", durationMs: Date.now() - start };
|
|
29701
30756
|
}
|
|
29702
30757
|
content = content.replace(oldStr, newStr);
|
|
29703
|
-
|
|
30758
|
+
writeFileSync12(targetPath, content, "utf-8");
|
|
29704
30759
|
return { success: true, output: `Edited ${rawPath}`, durationMs: Date.now() - start };
|
|
29705
30760
|
} catch (err) {
|
|
29706
30761
|
return { success: false, output: "", error: String(err), durationMs: Date.now() - start };
|
|
@@ -29754,7 +30809,7 @@ var init_dream_engine = __esm({
|
|
|
29754
30809
|
constructor(config, repoRoot) {
|
|
29755
30810
|
this.config = config;
|
|
29756
30811
|
this.repoRoot = repoRoot;
|
|
29757
|
-
this.dreamsDir =
|
|
30812
|
+
this.dreamsDir = join37(repoRoot, ".oa", "dreams");
|
|
29758
30813
|
this.state = {
|
|
29759
30814
|
mode: "default",
|
|
29760
30815
|
active: false,
|
|
@@ -29785,7 +30840,7 @@ var init_dream_engine = __esm({
|
|
|
29785
30840
|
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
29786
30841
|
results: []
|
|
29787
30842
|
};
|
|
29788
|
-
|
|
30843
|
+
mkdirSync13(this.dreamsDir, { recursive: true });
|
|
29789
30844
|
this.saveDreamState();
|
|
29790
30845
|
try {
|
|
29791
30846
|
for (let cycle = 1; cycle <= totalCycles; cycle++) {
|
|
@@ -29838,8 +30893,8 @@ ${result.summary}`;
|
|
|
29838
30893
|
if (mode !== "default" || cycle === totalCycles) {
|
|
29839
30894
|
renderDreamContraction(cycle);
|
|
29840
30895
|
const cycleSummary = this.buildCycleSummary(cycle, previousFindings);
|
|
29841
|
-
const summaryPath =
|
|
29842
|
-
|
|
30896
|
+
const summaryPath = join37(this.dreamsDir, `cycle-${cycle}-summary.md`);
|
|
30897
|
+
writeFileSync12(summaryPath, cycleSummary, "utf-8");
|
|
29843
30898
|
}
|
|
29844
30899
|
if (mode === "lucid" && !this.abortController.signal.aborted) {
|
|
29845
30900
|
this.saveVersionCheckpoint(cycle);
|
|
@@ -30051,7 +31106,7 @@ After synthesis, call task_complete with the final prioritized summary.`, toolMo
|
|
|
30051
31106
|
}
|
|
30052
31107
|
/** Build role-specific tool sets for swarm agents */
|
|
30053
31108
|
buildSwarmTools(role, _workspace) {
|
|
30054
|
-
const autoresearchDir =
|
|
31109
|
+
const autoresearchDir = join37(this.repoRoot, ".oa", "autoresearch");
|
|
30055
31110
|
const taskComplete = this.createSwarmTaskCompleteTool(role);
|
|
30056
31111
|
switch (role) {
|
|
30057
31112
|
case "researcher": {
|
|
@@ -30415,7 +31470,7 @@ INSTRUCTIONS:
|
|
|
30415
31470
|
2. Summarize the key learnings and next steps
|
|
30416
31471
|
|
|
30417
31472
|
Call task_complete with a human-readable summary of the autoresearch session.`, workspace, onEvent);
|
|
30418
|
-
const reportPath =
|
|
31473
|
+
const reportPath = join37(this.dreamsDir, `cycle-${cycleNum}-autoresearch-report.md`);
|
|
30419
31474
|
const report = `# Autoresearch Swarm Report \u2014 Cycle ${cycleNum}
|
|
30420
31475
|
|
|
30421
31476
|
**Date**: ${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]}
|
|
@@ -30437,8 +31492,8 @@ ${summaryResult}
|
|
|
30437
31492
|
*Generated by open-agents autoresearch swarm*
|
|
30438
31493
|
`;
|
|
30439
31494
|
try {
|
|
30440
|
-
|
|
30441
|
-
|
|
31495
|
+
mkdirSync13(this.dreamsDir, { recursive: true });
|
|
31496
|
+
writeFileSync12(reportPath, report, "utf-8");
|
|
30442
31497
|
} catch {
|
|
30443
31498
|
}
|
|
30444
31499
|
renderSwarmComplete(workspace);
|
|
@@ -30504,9 +31559,9 @@ ${summaryResult}
|
|
|
30504
31559
|
}
|
|
30505
31560
|
/** Save workspace backup for lucid mode */
|
|
30506
31561
|
saveVersionCheckpoint(cycle) {
|
|
30507
|
-
const checkpointDir =
|
|
31562
|
+
const checkpointDir = join37(this.dreamsDir, "checkpoints", `cycle-${cycle}`);
|
|
30508
31563
|
try {
|
|
30509
|
-
|
|
31564
|
+
mkdirSync13(checkpointDir, { recursive: true });
|
|
30510
31565
|
try {
|
|
30511
31566
|
const gitStatus = execSync23("git status --porcelain", {
|
|
30512
31567
|
cwd: this.repoRoot,
|
|
@@ -30523,10 +31578,10 @@ ${summaryResult}
|
|
|
30523
31578
|
encoding: "utf-8",
|
|
30524
31579
|
timeout: 5e3
|
|
30525
31580
|
}).trim();
|
|
30526
|
-
|
|
30527
|
-
|
|
30528
|
-
|
|
30529
|
-
|
|
31581
|
+
writeFileSync12(join37(checkpointDir, "git-status.txt"), gitStatus, "utf-8");
|
|
31582
|
+
writeFileSync12(join37(checkpointDir, "git-diff.patch"), gitDiff, "utf-8");
|
|
31583
|
+
writeFileSync12(join37(checkpointDir, "git-hash.txt"), gitHash, "utf-8");
|
|
31584
|
+
writeFileSync12(join37(checkpointDir, "checkpoint.json"), JSON.stringify({
|
|
30530
31585
|
cycle,
|
|
30531
31586
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
30532
31587
|
gitHash,
|
|
@@ -30534,7 +31589,7 @@ ${summaryResult}
|
|
|
30534
31589
|
}, null, 2), "utf-8");
|
|
30535
31590
|
renderInfo(`Checkpoint saved: cycle ${cycle} (${gitHash.slice(0, 8)})`);
|
|
30536
31591
|
} catch {
|
|
30537
|
-
|
|
31592
|
+
writeFileSync12(join37(checkpointDir, "checkpoint.json"), JSON.stringify({ cycle, timestamp: (/* @__PURE__ */ new Date()).toISOString(), mode: this.state.mode }, null, 2), "utf-8");
|
|
30538
31593
|
renderInfo(`Checkpoint saved: cycle ${cycle} (no git)`);
|
|
30539
31594
|
}
|
|
30540
31595
|
} catch (err) {
|
|
@@ -30592,14 +31647,14 @@ ${files.map((f) => `- [\`${f}\`](./${f})`).join("\n")}
|
|
|
30592
31647
|
---
|
|
30593
31648
|
*Auto-generated by open-agents dream engine*
|
|
30594
31649
|
`;
|
|
30595
|
-
|
|
31650
|
+
writeFileSync12(join37(this.dreamsDir, "PROPOSAL-INDEX.md"), index, "utf-8");
|
|
30596
31651
|
} catch {
|
|
30597
31652
|
}
|
|
30598
31653
|
}
|
|
30599
31654
|
/** Save dream state for resume/inspection */
|
|
30600
31655
|
saveDreamState() {
|
|
30601
31656
|
try {
|
|
30602
|
-
|
|
31657
|
+
writeFileSync12(join37(this.dreamsDir, "dream-state.json"), JSON.stringify(this.state, null, 2) + "\n", "utf-8");
|
|
30603
31658
|
} catch {
|
|
30604
31659
|
}
|
|
30605
31660
|
}
|
|
@@ -30752,8 +31807,8 @@ var init_bless_engine = __esm({
|
|
|
30752
31807
|
});
|
|
30753
31808
|
|
|
30754
31809
|
// packages/cli/dist/tui/dmn-engine.js
|
|
30755
|
-
import { existsSync as
|
|
30756
|
-
import { join as
|
|
31810
|
+
import { existsSync as existsSync28, readFileSync as readFileSync21, writeFileSync as writeFileSync13, mkdirSync as mkdirSync14, readdirSync as readdirSync11, unlinkSync as unlinkSync5 } from "node:fs";
|
|
31811
|
+
import { join as join38, basename as basename13 } from "node:path";
|
|
30757
31812
|
function buildDMNGatherPrompt(recentTaskSummaries, dueReminders, attentionItems, memoryTopics, capabilities, competence, reflectionBuffer) {
|
|
30758
31813
|
const competenceReport = competence.length > 0 ? competence.map((c3) => {
|
|
30759
31814
|
const rate = c3.attempts > 0 ? Math.round(c3.successes / c3.attempts * 100) : 0;
|
|
@@ -30985,9 +32040,9 @@ var init_dmn_engine = __esm({
|
|
|
30985
32040
|
constructor(config, repoRoot) {
|
|
30986
32041
|
this.config = config;
|
|
30987
32042
|
this.repoRoot = repoRoot;
|
|
30988
|
-
this.stateDir =
|
|
30989
|
-
this.historyDir =
|
|
30990
|
-
|
|
32043
|
+
this.stateDir = join38(repoRoot, ".oa", "dmn");
|
|
32044
|
+
this.historyDir = join38(repoRoot, ".oa", "dmn", "cycles");
|
|
32045
|
+
mkdirSync14(this.historyDir, { recursive: true });
|
|
30991
32046
|
this.loadState();
|
|
30992
32047
|
}
|
|
30993
32048
|
get stats() {
|
|
@@ -31576,11 +32631,11 @@ OUTPUT: Call task_complete with JSON:
|
|
|
31576
32631
|
async gatherMemoryTopics() {
|
|
31577
32632
|
const topics = [];
|
|
31578
32633
|
const dirs = [
|
|
31579
|
-
|
|
31580
|
-
|
|
32634
|
+
join38(this.repoRoot, ".oa", "memory"),
|
|
32635
|
+
join38(this.repoRoot, ".open-agents", "memory")
|
|
31581
32636
|
];
|
|
31582
32637
|
for (const dir of dirs) {
|
|
31583
|
-
if (!
|
|
32638
|
+
if (!existsSync28(dir))
|
|
31584
32639
|
continue;
|
|
31585
32640
|
try {
|
|
31586
32641
|
const files = readdirSync11(dir).filter((f) => f.endsWith(".json"));
|
|
@@ -31596,29 +32651,29 @@ OUTPUT: Call task_complete with JSON:
|
|
|
31596
32651
|
}
|
|
31597
32652
|
// ── State persistence ─────────────────────────────────────────────────
|
|
31598
32653
|
loadState() {
|
|
31599
|
-
const path =
|
|
31600
|
-
if (
|
|
32654
|
+
const path = join38(this.stateDir, "state.json");
|
|
32655
|
+
if (existsSync28(path)) {
|
|
31601
32656
|
try {
|
|
31602
|
-
this.state = JSON.parse(
|
|
32657
|
+
this.state = JSON.parse(readFileSync21(path, "utf-8"));
|
|
31603
32658
|
} catch {
|
|
31604
32659
|
}
|
|
31605
32660
|
}
|
|
31606
32661
|
}
|
|
31607
32662
|
saveState() {
|
|
31608
32663
|
try {
|
|
31609
|
-
|
|
32664
|
+
writeFileSync13(join38(this.stateDir, "state.json"), JSON.stringify(this.state, null, 2) + "\n", "utf-8");
|
|
31610
32665
|
} catch {
|
|
31611
32666
|
}
|
|
31612
32667
|
}
|
|
31613
32668
|
saveCycleResult(result) {
|
|
31614
32669
|
try {
|
|
31615
32670
|
const filename = `cycle-${result.cycleNumber}-${Date.now()}.json`;
|
|
31616
|
-
|
|
32671
|
+
writeFileSync13(join38(this.historyDir, filename), JSON.stringify(result, null, 2) + "\n", "utf-8");
|
|
31617
32672
|
const files = readdirSync11(this.historyDir).filter((f) => f.startsWith("cycle-") && f.endsWith(".json")).sort();
|
|
31618
32673
|
if (files.length > 50) {
|
|
31619
32674
|
for (const old of files.slice(0, files.length - 50)) {
|
|
31620
32675
|
try {
|
|
31621
|
-
unlinkSync5(
|
|
32676
|
+
unlinkSync5(join38(this.historyDir, old));
|
|
31622
32677
|
} catch {
|
|
31623
32678
|
}
|
|
31624
32679
|
}
|
|
@@ -31631,8 +32686,8 @@ OUTPUT: Call task_complete with JSON:
|
|
|
31631
32686
|
});
|
|
31632
32687
|
|
|
31633
32688
|
// packages/cli/dist/tui/snr-engine.js
|
|
31634
|
-
import { existsSync as
|
|
31635
|
-
import { join as
|
|
32689
|
+
import { existsSync as existsSync29, readdirSync as readdirSync12, readFileSync as readFileSync22 } from "node:fs";
|
|
32690
|
+
import { join as join39, basename as basename14 } from "node:path";
|
|
31636
32691
|
function computeDPrime(signalScores, noiseScores) {
|
|
31637
32692
|
if (signalScores.length === 0 || noiseScores.length === 0)
|
|
31638
32693
|
return 0;
|
|
@@ -31879,11 +32934,11 @@ Call task_complete with the JSON array when done.`, onEvent)
|
|
|
31879
32934
|
loadMemoryEntries(topics) {
|
|
31880
32935
|
const entries = [];
|
|
31881
32936
|
const dirs = [
|
|
31882
|
-
|
|
31883
|
-
|
|
32937
|
+
join39(this.repoRoot, ".oa", "memory"),
|
|
32938
|
+
join39(this.repoRoot, ".open-agents", "memory")
|
|
31884
32939
|
];
|
|
31885
32940
|
for (const dir of dirs) {
|
|
31886
|
-
if (!
|
|
32941
|
+
if (!existsSync29(dir))
|
|
31887
32942
|
continue;
|
|
31888
32943
|
try {
|
|
31889
32944
|
const files = readdirSync12(dir).filter((f) => f.endsWith(".json"));
|
|
@@ -31892,7 +32947,7 @@ Call task_complete with the JSON array when done.`, onEvent)
|
|
|
31892
32947
|
if (topics.length > 0 && !topics.includes(topic))
|
|
31893
32948
|
continue;
|
|
31894
32949
|
try {
|
|
31895
|
-
const data = JSON.parse(
|
|
32950
|
+
const data = JSON.parse(readFileSync22(join39(dir, f), "utf-8"));
|
|
31896
32951
|
for (const [key, val] of Object.entries(data)) {
|
|
31897
32952
|
const value = typeof val === "object" && val !== null && "value" in val ? String(val.value) : String(val);
|
|
31898
32953
|
entries.push({ topic, key, value });
|
|
@@ -32345,8 +33400,8 @@ var init_tool_policy = __esm({
|
|
|
32345
33400
|
});
|
|
32346
33401
|
|
|
32347
33402
|
// packages/cli/dist/tui/telegram-bridge.js
|
|
32348
|
-
import { mkdirSync as
|
|
32349
|
-
import { join as
|
|
33403
|
+
import { mkdirSync as mkdirSync15, existsSync as existsSync30, unlinkSync as unlinkSync6, readdirSync as readdirSync13, statSync as statSync10 } from "node:fs";
|
|
33404
|
+
import { join as join40, resolve as resolve24 } from "node:path";
|
|
32350
33405
|
import { writeFile as writeFileAsync } from "node:fs/promises";
|
|
32351
33406
|
function convertMarkdownToTelegramHTML(md) {
|
|
32352
33407
|
let html = md;
|
|
@@ -32674,7 +33729,7 @@ with summary "no_reply" to silently skip without responding.
|
|
|
32674
33729
|
this.polling = true;
|
|
32675
33730
|
this.abortController = new AbortController();
|
|
32676
33731
|
try {
|
|
32677
|
-
|
|
33732
|
+
mkdirSync15(this.mediaCacheDir, { recursive: true });
|
|
32678
33733
|
} catch {
|
|
32679
33734
|
}
|
|
32680
33735
|
this.mediaCacheCleanupTimer = setInterval(() => this.cleanupMediaCache(), 5 * 60 * 1e3);
|
|
@@ -33098,7 +34153,7 @@ Telegram admin: @${msg.username}` : `Telegram ${isGroup ? "group" : "public"} ch
|
|
|
33098
34153
|
return null;
|
|
33099
34154
|
const buffer = Buffer.from(await res.arrayBuffer());
|
|
33100
34155
|
const fileName = `${Date.now()}-${fileId.slice(0, 8)}${extension}`;
|
|
33101
|
-
const localPath =
|
|
34156
|
+
const localPath = join40(this.mediaCacheDir, fileName);
|
|
33102
34157
|
await writeFileAsync(localPath, buffer);
|
|
33103
34158
|
return localPath;
|
|
33104
34159
|
} catch {
|
|
@@ -34820,11 +35875,11 @@ var init_status_bar = __esm({
|
|
|
34820
35875
|
import * as readline2 from "node:readline";
|
|
34821
35876
|
import { Writable } from "node:stream";
|
|
34822
35877
|
import { cwd } from "node:process";
|
|
34823
|
-
import { resolve as resolve25, join as
|
|
35878
|
+
import { resolve as resolve25, join as join41, dirname as dirname13, extname as extname9 } from "node:path";
|
|
34824
35879
|
import { createRequire as createRequire2 } from "node:module";
|
|
34825
35880
|
import { fileURLToPath as fileURLToPath9 } from "node:url";
|
|
34826
|
-
import { readFileSync as
|
|
34827
|
-
import { existsSync as
|
|
35881
|
+
import { readFileSync as readFileSync23, rmSync as rmSync2, readdirSync as readdirSync14 } from "node:fs";
|
|
35882
|
+
import { existsSync as existsSync31 } from "node:fs";
|
|
34828
35883
|
function formatTimeAgo(date) {
|
|
34829
35884
|
const seconds = Math.floor((Date.now() - date.getTime()) / 1e3);
|
|
34830
35885
|
if (seconds < 60)
|
|
@@ -34841,14 +35896,14 @@ function formatTimeAgo(date) {
|
|
|
34841
35896
|
function getVersion() {
|
|
34842
35897
|
try {
|
|
34843
35898
|
const require2 = createRequire2(import.meta.url);
|
|
34844
|
-
const thisDir =
|
|
35899
|
+
const thisDir = dirname13(fileURLToPath9(import.meta.url));
|
|
34845
35900
|
const candidates = [
|
|
34846
|
-
|
|
34847
|
-
|
|
34848
|
-
|
|
35901
|
+
join41(thisDir, "..", "package.json"),
|
|
35902
|
+
join41(thisDir, "..", "..", "package.json"),
|
|
35903
|
+
join41(thisDir, "..", "..", "..", "package.json")
|
|
34849
35904
|
];
|
|
34850
35905
|
for (const pkgPath of candidates) {
|
|
34851
|
-
if (
|
|
35906
|
+
if (existsSync31(pkgPath)) {
|
|
34852
35907
|
const pkg = require2(pkgPath);
|
|
34853
35908
|
if (pkg.name === "open-agents-ai" || pkg.name === "@open-agents/cli") {
|
|
34854
35909
|
return pkg.version ?? "0.0.0";
|
|
@@ -35042,15 +36097,15 @@ Use task_status("${taskId}") or task_output("${taskId}") to check progress.`
|
|
|
35042
36097
|
function gatherMemorySnippets(root) {
|
|
35043
36098
|
const snippets = [];
|
|
35044
36099
|
const dirs = [
|
|
35045
|
-
|
|
35046
|
-
|
|
36100
|
+
join41(root, ".oa", "memory"),
|
|
36101
|
+
join41(root, ".open-agents", "memory")
|
|
35047
36102
|
];
|
|
35048
36103
|
for (const dir of dirs) {
|
|
35049
|
-
if (!
|
|
36104
|
+
if (!existsSync31(dir))
|
|
35050
36105
|
continue;
|
|
35051
36106
|
try {
|
|
35052
36107
|
for (const f of readdirSync14(dir).filter((f2) => f2.endsWith(".json"))) {
|
|
35053
|
-
const data = JSON.parse(
|
|
36108
|
+
const data = JSON.parse(readFileSync23(join41(dir, f), "utf-8"));
|
|
35054
36109
|
for (const val of Object.values(data)) {
|
|
35055
36110
|
const v = typeof val === "object" && val !== null && "value" in val ? String(val.value) : String(val);
|
|
35056
36111
|
if (v.length > 10)
|
|
@@ -35663,6 +36718,9 @@ async function startInteractive(config, repoPath) {
|
|
|
35663
36718
|
const voiceEngine = new VoiceEngine();
|
|
35664
36719
|
let voiceSession = null;
|
|
35665
36720
|
let exposeGateway = null;
|
|
36721
|
+
let peerMesh = null;
|
|
36722
|
+
let inferenceRouter = null;
|
|
36723
|
+
const secretVault = new SecretVault(join41(repoRoot, ".oa", "vault.enc"));
|
|
35666
36724
|
let adminSessionKey = null;
|
|
35667
36725
|
const callSubAgents = /* @__PURE__ */ new Map();
|
|
35668
36726
|
const streamRenderer = new StreamRenderer();
|
|
@@ -35824,6 +36882,9 @@ Rationale: ${proposal.rationale}${provenanceNote}`;
|
|
|
35824
36882
|
"/stop",
|
|
35825
36883
|
"/resume",
|
|
35826
36884
|
"/expose",
|
|
36885
|
+
"/p2p",
|
|
36886
|
+
"/secrets",
|
|
36887
|
+
"/vault",
|
|
35827
36888
|
"/full-send-bless",
|
|
35828
36889
|
"/bless",
|
|
35829
36890
|
"/telegram",
|
|
@@ -36559,6 +37620,80 @@ Respond concisely and safely. Remember: you are talking to the general public.`;
|
|
|
36559
37620
|
stats: exposeGateway.formatStats()
|
|
36560
37621
|
};
|
|
36561
37622
|
},
|
|
37623
|
+
// ── P2P mesh controls ────────────────────────────────────────────────
|
|
37624
|
+
async p2pStart(bootstrapPeers) {
|
|
37625
|
+
if (peerMesh)
|
|
37626
|
+
throw new Error("P2P mesh is already running");
|
|
37627
|
+
let models = [];
|
|
37628
|
+
try {
|
|
37629
|
+
const resp = await fetch(`${commandCtx.config.backendUrl ?? "http://127.0.0.1:11434"}/api/tags`);
|
|
37630
|
+
const data = await resp.json();
|
|
37631
|
+
models = data.models?.map((m) => m.name) ?? [];
|
|
37632
|
+
} catch {
|
|
37633
|
+
}
|
|
37634
|
+
peerMesh = new PeerMesh({
|
|
37635
|
+
displayName: commandCtx.config.model ?? "open-agents-node",
|
|
37636
|
+
capabilities: { models, gpuVramGB: 0, teeAttested: false, maxContextTokens: 32768, load: 0 },
|
|
37637
|
+
bootstrapPeers
|
|
37638
|
+
});
|
|
37639
|
+
const port = await peerMesh.start();
|
|
37640
|
+
inferenceRouter = new InferenceRouter({
|
|
37641
|
+
mesh: peerMesh,
|
|
37642
|
+
vault: secretVault
|
|
37643
|
+
});
|
|
37644
|
+
return { peerId: peerMesh.peerId, port };
|
|
37645
|
+
},
|
|
37646
|
+
async p2pStop() {
|
|
37647
|
+
if (peerMesh) {
|
|
37648
|
+
await peerMesh.stop();
|
|
37649
|
+
peerMesh = null;
|
|
37650
|
+
inferenceRouter = null;
|
|
37651
|
+
}
|
|
37652
|
+
},
|
|
37653
|
+
async p2pConnect(endpoint, trust) {
|
|
37654
|
+
if (!peerMesh)
|
|
37655
|
+
throw new Error("P2P mesh is not running \u2014 use /p2p start first");
|
|
37656
|
+
const trustTier = trust ?? "public";
|
|
37657
|
+
const peer = await peerMesh.connectToPeer(endpoint, trustTier);
|
|
37658
|
+
return peer?.peerId ?? null;
|
|
37659
|
+
},
|
|
37660
|
+
p2pStatus() {
|
|
37661
|
+
if (!peerMesh)
|
|
37662
|
+
return "P2P mesh is not running.";
|
|
37663
|
+
const peers = peerMesh.getPeers();
|
|
37664
|
+
const connected = peers.filter((p) => p.connected);
|
|
37665
|
+
const lines = [
|
|
37666
|
+
`Node: ${peerMesh.peerId} (port ${peerMesh.port})`,
|
|
37667
|
+
`Peers: ${connected.length} connected / ${peers.length} known`
|
|
37668
|
+
];
|
|
37669
|
+
if (inferenceRouter) {
|
|
37670
|
+
const stats = inferenceRouter.getStats();
|
|
37671
|
+
lines.push(`Routed: ${stats.totalRouted} | Redacted: ${stats.totalRedacted} | Errors: ${stats.totalErrors}`);
|
|
37672
|
+
if (stats.availableModels.length > 0) {
|
|
37673
|
+
lines.push(`Models: ${stats.availableModels.join(", ")}`);
|
|
37674
|
+
}
|
|
37675
|
+
}
|
|
37676
|
+
for (const p of connected) {
|
|
37677
|
+
lines.push(` ${p.peerId.slice(0, 8)}\u2026 [${p.trust}] ${p.latencyMs}ms \u2014 ${p.capabilities.models.join(", ") || "no models"}`);
|
|
37678
|
+
}
|
|
37679
|
+
return lines.join("\n");
|
|
37680
|
+
},
|
|
37681
|
+
isP2PActive() {
|
|
37682
|
+
return peerMesh !== null;
|
|
37683
|
+
},
|
|
37684
|
+
// ── Secret vault controls ────────────────────────────────────────────
|
|
37685
|
+
secretSet(name, value, minTrust) {
|
|
37686
|
+
secretVault.set(name, value, minTrust ?? "verified");
|
|
37687
|
+
},
|
|
37688
|
+
secretDelete(name) {
|
|
37689
|
+
return secretVault.delete(name);
|
|
37690
|
+
},
|
|
37691
|
+
secretList() {
|
|
37692
|
+
return secretVault.list();
|
|
37693
|
+
},
|
|
37694
|
+
secretImportEnv() {
|
|
37695
|
+
return secretVault.importFromEnv();
|
|
37696
|
+
},
|
|
36562
37697
|
async sendCallButton(chatId, url) {
|
|
36563
37698
|
if (telegramBridge?.isActive) {
|
|
36564
37699
|
await telegramBridge.sendCallButton(chatId, url);
|
|
@@ -36628,8 +37763,8 @@ Respond concisely and safely. Remember: you are talking to the general public.`;
|
|
|
36628
37763
|
return true;
|
|
36629
37764
|
},
|
|
36630
37765
|
destroyProject() {
|
|
36631
|
-
const oaPath =
|
|
36632
|
-
if (
|
|
37766
|
+
const oaPath = join41(repoRoot, OA_DIR);
|
|
37767
|
+
if (existsSync31(oaPath)) {
|
|
36633
37768
|
try {
|
|
36634
37769
|
rmSync2(oaPath, { recursive: true, force: true });
|
|
36635
37770
|
writeContent(() => renderInfo(`Removed ${OA_DIR}/ directory.`));
|
|
@@ -36913,13 +38048,13 @@ Execute this skill now. Follow the behavioral guidance above.`;
|
|
|
36913
38048
|
}
|
|
36914
38049
|
}
|
|
36915
38050
|
const cleanPath = input.replace(/^['"]|['"]$/g, "").trim();
|
|
36916
|
-
const isImage = isImagePath(cleanPath) &&
|
|
36917
|
-
const isMedia = !isImage && isTranscribablePath(cleanPath) &&
|
|
38051
|
+
const isImage = isImagePath(cleanPath) && existsSync31(resolve25(repoRoot, cleanPath));
|
|
38052
|
+
const isMedia = !isImage && isTranscribablePath(cleanPath) && existsSync31(resolve25(repoRoot, cleanPath));
|
|
36918
38053
|
if (activeTask) {
|
|
36919
38054
|
if (isImage) {
|
|
36920
38055
|
try {
|
|
36921
38056
|
const imgPath = resolve25(repoRoot, cleanPath);
|
|
36922
|
-
const imgBuffer =
|
|
38057
|
+
const imgBuffer = readFileSync23(imgPath);
|
|
36923
38058
|
const base64 = imgBuffer.toString("base64");
|
|
36924
38059
|
const ext = extname9(cleanPath).toLowerCase();
|
|
36925
38060
|
const mime = ext === ".png" ? "image/png" : ext === ".gif" ? "image/gif" : ext === ".webp" ? "image/webp" : "image/jpeg";
|
|
@@ -37102,13 +38237,13 @@ NEW TASK: ${fullInput}`;
|
|
|
37102
38237
|
writeContent(() => renderError(errMsg));
|
|
37103
38238
|
if (failureStore) {
|
|
37104
38239
|
try {
|
|
37105
|
-
const { createHash:
|
|
38240
|
+
const { createHash: createHash4 } = await import("node:crypto");
|
|
37106
38241
|
failureStore.insert({
|
|
37107
38242
|
taskId: "",
|
|
37108
38243
|
sessionId: `${Date.now()}`,
|
|
37109
38244
|
repoRoot,
|
|
37110
38245
|
failureType: "runtime-error",
|
|
37111
|
-
fingerprint:
|
|
38246
|
+
fingerprint: createHash4("sha256").update(errMsg.slice(0, 200)).digest("hex").slice(0, 16),
|
|
37112
38247
|
filePath: null,
|
|
37113
38248
|
errorMessage: errMsg.slice(0, 500),
|
|
37114
38249
|
context: null,
|
|
@@ -37229,6 +38364,12 @@ Rationale: ${proposal.rationale}${provenanceNote}`;
|
|
|
37229
38364
|
exposeGateway.stop().catch(() => {
|
|
37230
38365
|
});
|
|
37231
38366
|
}
|
|
38367
|
+
if (peerMesh) {
|
|
38368
|
+
peerMesh.stop().catch(() => {
|
|
38369
|
+
});
|
|
38370
|
+
peerMesh = null;
|
|
38371
|
+
inferenceRouter = null;
|
|
38372
|
+
}
|
|
37232
38373
|
statusBar.deactivate();
|
|
37233
38374
|
const closeRows = process.stdout.rows ?? 24;
|
|
37234
38375
|
process.stdout.write(`\x1B[${closeRows - 4};1H\x1B[J
|
|
@@ -37318,6 +38459,7 @@ var init_interactive = __esm({
|
|
|
37318
38459
|
init_listen();
|
|
37319
38460
|
init_voice_session();
|
|
37320
38461
|
init_expose();
|
|
38462
|
+
init_p2p();
|
|
37321
38463
|
init_call_agent();
|
|
37322
38464
|
init_config();
|
|
37323
38465
|
init_updater();
|
|
@@ -37375,8 +38517,8 @@ var init_run = __esm({
|
|
|
37375
38517
|
import { glob } from "glob";
|
|
37376
38518
|
import ignore from "ignore";
|
|
37377
38519
|
import { readFile as readFile14, stat as stat4 } from "node:fs/promises";
|
|
37378
|
-
import { createHash } from "node:crypto";
|
|
37379
|
-
import { join as
|
|
38520
|
+
import { createHash as createHash3 } from "node:crypto";
|
|
38521
|
+
import { join as join42, relative as relative3, extname as extname10, basename as basename15 } from "node:path";
|
|
37380
38522
|
var DEFAULT_EXCLUDE, LANGUAGE_MAP, CodebaseIndexer;
|
|
37381
38523
|
var init_codebase_indexer = __esm({
|
|
37382
38524
|
"packages/indexer/dist/codebase-indexer.js"() {
|
|
@@ -37420,7 +38562,7 @@ var init_codebase_indexer = __esm({
|
|
|
37420
38562
|
const ig = ignore.default();
|
|
37421
38563
|
if (this.config.respectGitignore) {
|
|
37422
38564
|
try {
|
|
37423
|
-
const gitignoreContent = await readFile14(
|
|
38565
|
+
const gitignoreContent = await readFile14(join42(this.config.rootDir, ".gitignore"), "utf-8");
|
|
37424
38566
|
ig.add(gitignoreContent);
|
|
37425
38567
|
} catch {
|
|
37426
38568
|
}
|
|
@@ -37435,13 +38577,13 @@ var init_codebase_indexer = __esm({
|
|
|
37435
38577
|
for (const relativePath of files) {
|
|
37436
38578
|
if (ig.ignores(relativePath))
|
|
37437
38579
|
continue;
|
|
37438
|
-
const fullPath =
|
|
38580
|
+
const fullPath = join42(this.config.rootDir, relativePath);
|
|
37439
38581
|
try {
|
|
37440
38582
|
const fileStat = await stat4(fullPath);
|
|
37441
38583
|
if (fileStat.size > this.config.maxFileSize)
|
|
37442
38584
|
continue;
|
|
37443
38585
|
const content = await readFile14(fullPath);
|
|
37444
|
-
const hash =
|
|
38586
|
+
const hash = createHash3("sha256").update(content).digest("hex");
|
|
37445
38587
|
const ext = extname10(relativePath);
|
|
37446
38588
|
indexed.push({
|
|
37447
38589
|
path: fullPath,
|
|
@@ -37481,7 +38623,7 @@ var init_codebase_indexer = __esm({
|
|
|
37481
38623
|
if (!child) {
|
|
37482
38624
|
child = {
|
|
37483
38625
|
name: part,
|
|
37484
|
-
path:
|
|
38626
|
+
path: join42(current.path, part),
|
|
37485
38627
|
type: "directory",
|
|
37486
38628
|
children: []
|
|
37487
38629
|
};
|
|
@@ -37564,13 +38706,13 @@ __export(index_repo_exports, {
|
|
|
37564
38706
|
indexRepoCommand: () => indexRepoCommand
|
|
37565
38707
|
});
|
|
37566
38708
|
import { resolve as resolve26 } from "node:path";
|
|
37567
|
-
import { existsSync as
|
|
38709
|
+
import { existsSync as existsSync32, statSync as statSync11 } from "node:fs";
|
|
37568
38710
|
import { cwd as cwd2 } from "node:process";
|
|
37569
38711
|
async function indexRepoCommand(opts, _config) {
|
|
37570
38712
|
const repoRoot = resolve26(opts.repoPath ?? cwd2());
|
|
37571
38713
|
printHeader("Index Repository");
|
|
37572
38714
|
printInfo(`Indexing: ${repoRoot}`);
|
|
37573
|
-
if (!
|
|
38715
|
+
if (!existsSync32(repoRoot)) {
|
|
37574
38716
|
printError(`Path does not exist: ${repoRoot}`);
|
|
37575
38717
|
process.exit(1);
|
|
37576
38718
|
}
|
|
@@ -37822,7 +38964,7 @@ var config_exports = {};
|
|
|
37822
38964
|
__export(config_exports, {
|
|
37823
38965
|
configCommand: () => configCommand
|
|
37824
38966
|
});
|
|
37825
|
-
import { join as
|
|
38967
|
+
import { join as join43, resolve as resolve27 } from "node:path";
|
|
37826
38968
|
import { homedir as homedir13 } from "node:os";
|
|
37827
38969
|
import { cwd as cwd3 } from "node:process";
|
|
37828
38970
|
function redactIfSensitive(key, value) {
|
|
@@ -37881,7 +39023,7 @@ function handleShow(opts, config) {
|
|
|
37881
39023
|
}
|
|
37882
39024
|
}
|
|
37883
39025
|
printSection("Config File");
|
|
37884
|
-
printInfo(`~/.open-agents/config.json (${
|
|
39026
|
+
printInfo(`~/.open-agents/config.json (${join43(homedir13(), ".open-agents", "config.json")})`);
|
|
37885
39027
|
printSection("Priority Chain");
|
|
37886
39028
|
printInfo(" 1. CLI flags (--model, --backend-url, etc.)");
|
|
37887
39029
|
printInfo(" 2. Project .oa/settings.json (--local)");
|
|
@@ -37920,7 +39062,7 @@ function handleSet(opts, _config) {
|
|
|
37920
39062
|
const coerced = coerceForSettings(key, value);
|
|
37921
39063
|
saveProjectSettings(repoRoot, { [key]: coerced });
|
|
37922
39064
|
printSuccess(`Project override set: ${key} = ${redactIfSensitive(key, value)}`);
|
|
37923
|
-
printInfo(`Saved to ${
|
|
39065
|
+
printInfo(`Saved to ${join43(repoRoot, ".oa", "settings.json")}`);
|
|
37924
39066
|
printInfo("This override applies only when running in this workspace.");
|
|
37925
39067
|
} catch (err) {
|
|
37926
39068
|
printError(`Failed to save: ${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -38139,8 +39281,8 @@ __export(eval_exports, {
|
|
|
38139
39281
|
evalCommand: () => evalCommand
|
|
38140
39282
|
});
|
|
38141
39283
|
import { tmpdir as tmpdir7 } from "node:os";
|
|
38142
|
-
import { mkdirSync as
|
|
38143
|
-
import { join as
|
|
39284
|
+
import { mkdirSync as mkdirSync16, writeFileSync as writeFileSync14 } from "node:fs";
|
|
39285
|
+
import { join as join44 } from "node:path";
|
|
38144
39286
|
async function evalCommand(opts, config) {
|
|
38145
39287
|
const suiteName = opts.suite ?? "basic";
|
|
38146
39288
|
const suite = SUITES[suiteName];
|
|
@@ -38261,9 +39403,9 @@ async function evalCommand(opts, config) {
|
|
|
38261
39403
|
process.exit(failed > 0 ? 1 : 0);
|
|
38262
39404
|
}
|
|
38263
39405
|
function createTempEvalRepo() {
|
|
38264
|
-
const dir =
|
|
38265
|
-
|
|
38266
|
-
|
|
39406
|
+
const dir = join44(tmpdir7(), `open-agents-eval-${Date.now()}`);
|
|
39407
|
+
mkdirSync16(dir, { recursive: true });
|
|
39408
|
+
writeFileSync14(join44(dir, "package.json"), JSON.stringify({ name: "eval-repo", version: "0.0.0" }, null, 2) + "\n", "utf8");
|
|
38267
39409
|
return dir;
|
|
38268
39410
|
}
|
|
38269
39411
|
var BASIC_SUITE, FULL_SUITE, SUITES;
|
|
@@ -38323,7 +39465,7 @@ init_updater();
|
|
|
38323
39465
|
import { parseArgs as nodeParseArgs2 } from "node:util";
|
|
38324
39466
|
import { createRequire as createRequire3 } from "node:module";
|
|
38325
39467
|
import { fileURLToPath as fileURLToPath10 } from "node:url";
|
|
38326
|
-
import { dirname as
|
|
39468
|
+
import { dirname as dirname14, join as join45 } from "node:path";
|
|
38327
39469
|
|
|
38328
39470
|
// packages/cli/dist/cli.js
|
|
38329
39471
|
import { createInterface } from "node:readline";
|
|
@@ -38430,7 +39572,7 @@ init_output();
|
|
|
38430
39572
|
function getVersion2() {
|
|
38431
39573
|
try {
|
|
38432
39574
|
const require2 = createRequire3(import.meta.url);
|
|
38433
|
-
const pkgPath =
|
|
39575
|
+
const pkgPath = join45(dirname14(fileURLToPath10(import.meta.url)), "..", "package.json");
|
|
38434
39576
|
const pkg = require2(pkgPath);
|
|
38435
39577
|
return pkg.version;
|
|
38436
39578
|
} catch {
|