@lincoln504/pi-research 1.7.8 → 1.7.10
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/README.md +5 -1
- package/agent-skill/pi-research/README.md +3 -3
- package/agent-skill/pi-research/SKILL.md +1 -1
- package/dist/cli-bin.mjs +3 -0
- package/dist/cli.mjs +673 -383
- package/dist/pdf-extract-worker.mjs +1 -1
- package/dist/thread-worker.mjs +6 -6
- package/docs/AGENT-SKILL.md +6 -4
- package/docs/ARCHITECTURE.md +64 -50
- package/docs/CHANGELOG.md +41 -0
- package/docs/CONFIGURATION.md +2 -2
- package/docs/PI-EXTENSION.md +4 -0
- package/docs/SDK.md +10 -7
- package/docs/THIRD-PARTY-NOTICES.md +11 -7
- package/package.json +10 -8
- package/scripts/camoufox-layout.cjs +455 -0
- package/scripts/cleanup.cjs +72 -29
- package/scripts/setup.cjs +73 -111
- package/src/cli-bin.mjs +3 -0
- package/src/cli.ts +12 -13
- package/src/core/pi-version.ts +9 -1
- package/src/healthcheck/index.ts +1 -1
- package/src/infrastructure/browser/browser-cleanup.ts +1 -1
- package/src/infrastructure/browser/browser-error-utils.ts +5 -4
- package/src/infrastructure/browser/camoufox-layout.ts +450 -0
- package/src/infrastructure/browser/config.ts +46 -103
- package/src/infrastructure/browser/ensure-browser.ts +90 -52
- package/src/infrastructure/browser/thread-worker-browser.ts +38 -42
- package/src/infrastructure/browser/thread-worker.mjs +6 -6
- package/src/infrastructure/knowledge-store-service.ts +6 -6
- package/src/orchestration/researcher-executor.ts +47 -1
- package/src/tui/research-health.ts +1 -1
- package/src/utils/log-utils.ts +1 -1
- package/src/web-research/browser-search.ts +8 -10
- package/src/web-research/pdf-extract-worker.mjs +1 -1
- package/src/web-research/scraper-utils.ts +11 -2
- package/src/web-research/web-scraper.ts +1 -1
package/dist/cli.mjs
CHANGED
|
@@ -277,7 +277,7 @@ var init_log_utils = __esm({
|
|
|
277
277
|
"gi"
|
|
278
278
|
);
|
|
279
279
|
KNOWN_TOKEN_PATTERN = new RegExp(
|
|
280
|
-
`${B_LEFT}(sk-[A-Za-z0-9_-]{16,}|gh[posru]_[A-Za-z0-9]{16,}|github_pat_[A-Za-z0-9_]{20,}|AKIA[0-9A-Z]{16}|xox[baprs]-[A-Za-z0-9-]{10,})${B_RIGHT}`,
|
|
280
|
+
`${B_LEFT}(sk-[A-Za-z0-9_-]{16,}|gh[posru]_[A-Za-z0-9]{16,}|github_pat_[A-Za-z0-9_]{20,}|AKIA[0-9A-Z]{16}|AIza[0-9A-Za-z_-]{35}|npm_[A-Za-z0-9]{36}|hf_[A-Za-z0-9]{30,}|xox[baprs]-[A-Za-z0-9-]{10,})${B_RIGHT}`,
|
|
281
281
|
"g"
|
|
282
282
|
);
|
|
283
283
|
JWT_PATTERN = new RegExp(
|
|
@@ -4563,12 +4563,12 @@ async function isModelCached(model) {
|
|
|
4563
4563
|
const cacheDir = env.cacheDir;
|
|
4564
4564
|
if (!cacheDir) return false;
|
|
4565
4565
|
const { access: access3, readdir: readdir6, readFile: readFile7, stat: stat6, rm: rm3 } = await import("node:fs/promises");
|
|
4566
|
-
const
|
|
4567
|
-
const modelDir =
|
|
4568
|
-
const onnxDir =
|
|
4569
|
-
await access3(
|
|
4566
|
+
const path21 = await import("node:path");
|
|
4567
|
+
const modelDir = path21.default.join(cacheDir, model);
|
|
4568
|
+
const onnxDir = path21.default.join(modelDir, "onnx");
|
|
4569
|
+
await access3(path21.default.join(onnxDir, "model.onnx"));
|
|
4570
4570
|
try {
|
|
4571
|
-
const cfgRaw = await readFile7(
|
|
4571
|
+
const cfgRaw = await readFile7(path21.default.join(modelDir, "config.json"), "utf-8");
|
|
4572
4572
|
JSON.parse(cfgRaw);
|
|
4573
4573
|
} catch {
|
|
4574
4574
|
return false;
|
|
@@ -4576,7 +4576,7 @@ async function isModelCached(model) {
|
|
|
4576
4576
|
const tokenizerFiles = ["tokenizer.json", "tokenizer.model", "vocab.txt"];
|
|
4577
4577
|
let hasTokenizer = false;
|
|
4578
4578
|
for (const f of tokenizerFiles) {
|
|
4579
|
-
const st = await stat6(
|
|
4579
|
+
const st = await stat6(path21.default.join(modelDir, f)).catch(() => null);
|
|
4580
4580
|
if (st && st.size > 0) {
|
|
4581
4581
|
hasTokenizer = true;
|
|
4582
4582
|
break;
|
|
@@ -4584,7 +4584,7 @@ async function isModelCached(model) {
|
|
|
4584
4584
|
}
|
|
4585
4585
|
if (!hasTokenizer) return false;
|
|
4586
4586
|
const entries = await readdir6(onnxDir).catch(() => []);
|
|
4587
|
-
const onnxPath =
|
|
4587
|
+
const onnxPath = path21.default.join(onnxDir, "model.onnx");
|
|
4588
4588
|
const onnxStat = await stat6(onnxPath).catch(() => null);
|
|
4589
4589
|
if (onnxStat && onnxStat.size > 0 && onnxStat.size <= 1024 * 1024) {
|
|
4590
4590
|
try {
|
|
@@ -4604,7 +4604,7 @@ async function isModelCached(model) {
|
|
|
4604
4604
|
}
|
|
4605
4605
|
const hasExternalWeights = entries.includes("model.onnx_data");
|
|
4606
4606
|
if (hasExternalWeights) {
|
|
4607
|
-
const dataStat = await stat6(
|
|
4607
|
+
const dataStat = await stat6(path21.default.join(onnxDir, "model.onnx_data")).catch(() => null);
|
|
4608
4608
|
if (!dataStat || dataStat.size === 0) return false;
|
|
4609
4609
|
}
|
|
4610
4610
|
const partialArtifacts = entries.filter(
|
|
@@ -4613,7 +4613,7 @@ async function isModelCached(model) {
|
|
|
4613
4613
|
if (partialArtifacts.length > 0) {
|
|
4614
4614
|
if (hasExternalWeights) {
|
|
4615
4615
|
for (const f of partialArtifacts) {
|
|
4616
|
-
await rm3(
|
|
4616
|
+
await rm3(path21.default.join(onnxDir, f), { force: true }).catch(() => {
|
|
4617
4617
|
});
|
|
4618
4618
|
}
|
|
4619
4619
|
} else {
|
|
@@ -4639,10 +4639,10 @@ async function purgeModelCache(model) {
|
|
|
4639
4639
|
const cacheDir = env.cacheDir;
|
|
4640
4640
|
if (!cacheDir) return;
|
|
4641
4641
|
const { rm: rm3 } = await import("node:fs/promises");
|
|
4642
|
-
const
|
|
4643
|
-
const resolvedCache =
|
|
4644
|
-
const modelDir =
|
|
4645
|
-
if (!model ||
|
|
4642
|
+
const path21 = await import("node:path");
|
|
4643
|
+
const resolvedCache = path21.default.resolve(cacheDir);
|
|
4644
|
+
const modelDir = path21.default.resolve(resolvedCache, model);
|
|
4645
|
+
if (!model || path21.default.isAbsolute(model) || modelDir === resolvedCache || !modelDir.startsWith(resolvedCache + path21.default.sep)) {
|
|
4646
4646
|
logger.warn(`[embedder] Refusing to purge unsafe model-cache path for model id ${JSON.stringify(model)}`);
|
|
4647
4647
|
return;
|
|
4648
4648
|
}
|
|
@@ -6219,7 +6219,7 @@ var init_embedding_client = __esm({
|
|
|
6219
6219
|
}
|
|
6220
6220
|
}
|
|
6221
6221
|
// ---- Internal HTTP helpers ----
|
|
6222
|
-
getRequest(
|
|
6222
|
+
getRequest(path21, timeoutMs = 12e4) {
|
|
6223
6223
|
return new Promise((resolve9, reject) => {
|
|
6224
6224
|
let resolved = false;
|
|
6225
6225
|
let req;
|
|
@@ -6227,7 +6227,7 @@ var init_embedding_client = __esm({
|
|
|
6227
6227
|
if (!resolved) {
|
|
6228
6228
|
resolved = true;
|
|
6229
6229
|
req?.destroy();
|
|
6230
|
-
reject(new Error(`[EmbeddingClient] GET ${
|
|
6230
|
+
reject(new Error(`[EmbeddingClient] GET ${path21} timed out after ${timeoutMs}ms`));
|
|
6231
6231
|
}
|
|
6232
6232
|
}, timeoutMs);
|
|
6233
6233
|
if (timer.unref) timer.unref();
|
|
@@ -6235,7 +6235,7 @@ var init_embedding_client = __esm({
|
|
|
6235
6235
|
{
|
|
6236
6236
|
hostname: "127.0.0.1",
|
|
6237
6237
|
port: this.port,
|
|
6238
|
-
path:
|
|
6238
|
+
path: path21,
|
|
6239
6239
|
method: "GET",
|
|
6240
6240
|
headers: this.authHeaders()
|
|
6241
6241
|
},
|
|
@@ -6251,19 +6251,19 @@ var init_embedding_client = __esm({
|
|
|
6251
6251
|
try {
|
|
6252
6252
|
const parsed = JSON.parse(collected.toString());
|
|
6253
6253
|
if (res.statusCode !== 200) {
|
|
6254
|
-
reject(new Error(`[EmbeddingClient] GET ${
|
|
6254
|
+
reject(new Error(`[EmbeddingClient] GET ${path21} returned HTTP ${res.statusCode}`));
|
|
6255
6255
|
} else {
|
|
6256
6256
|
resolve9(parsed);
|
|
6257
6257
|
}
|
|
6258
6258
|
} catch (_e) {
|
|
6259
|
-
reject(new Error(`[EmbeddingClient] Failed to parse GET ${
|
|
6259
|
+
reject(new Error(`[EmbeddingClient] Failed to parse GET ${path21} response: ${collected.toString()}`));
|
|
6260
6260
|
}
|
|
6261
6261
|
});
|
|
6262
6262
|
res.on("error", (err) => {
|
|
6263
6263
|
if (resolved) return;
|
|
6264
6264
|
resolved = true;
|
|
6265
6265
|
clearTimeout(timer);
|
|
6266
|
-
reject(new Error(`[EmbeddingClient] GET ${
|
|
6266
|
+
reject(new Error(`[EmbeddingClient] GET ${path21} response error: ${err.message}`));
|
|
6267
6267
|
});
|
|
6268
6268
|
}
|
|
6269
6269
|
);
|
|
@@ -6271,12 +6271,12 @@ var init_embedding_client = __esm({
|
|
|
6271
6271
|
if (resolved) return;
|
|
6272
6272
|
resolved = true;
|
|
6273
6273
|
clearTimeout(timer);
|
|
6274
|
-
reject(new Error(`[EmbeddingClient] GET ${
|
|
6274
|
+
reject(new Error(`[EmbeddingClient] GET ${path21} request error: ${err.message}`));
|
|
6275
6275
|
});
|
|
6276
6276
|
req.end();
|
|
6277
6277
|
});
|
|
6278
6278
|
}
|
|
6279
|
-
request(
|
|
6279
|
+
request(path21, data) {
|
|
6280
6280
|
return new Promise((resolve9, reject) => {
|
|
6281
6281
|
const timeoutMs = 12e4;
|
|
6282
6282
|
let resolved = false;
|
|
@@ -6285,7 +6285,7 @@ var init_embedding_client = __esm({
|
|
|
6285
6285
|
if (!resolved) {
|
|
6286
6286
|
resolved = true;
|
|
6287
6287
|
req?.destroy();
|
|
6288
|
-
reject(new Error(`[EmbeddingClient] POST ${
|
|
6288
|
+
reject(new Error(`[EmbeddingClient] POST ${path21} timed out after ${timeoutMs}ms`));
|
|
6289
6289
|
}
|
|
6290
6290
|
}, timeoutMs);
|
|
6291
6291
|
if (timer.unref) timer.unref();
|
|
@@ -6294,7 +6294,7 @@ var init_embedding_client = __esm({
|
|
|
6294
6294
|
{
|
|
6295
6295
|
hostname: "127.0.0.1",
|
|
6296
6296
|
port: this.port,
|
|
6297
|
-
path:
|
|
6297
|
+
path: path21,
|
|
6298
6298
|
method: "POST",
|
|
6299
6299
|
headers: {
|
|
6300
6300
|
"Content-Type": "application/json",
|
|
@@ -6320,14 +6320,14 @@ var init_embedding_client = __esm({
|
|
|
6320
6320
|
resolve9(parsed);
|
|
6321
6321
|
}
|
|
6322
6322
|
} catch (_e) {
|
|
6323
|
-
reject(new Error(`[EmbeddingClient] Failed to parse POST ${
|
|
6323
|
+
reject(new Error(`[EmbeddingClient] Failed to parse POST ${path21} response: ${collected.toString()}`));
|
|
6324
6324
|
}
|
|
6325
6325
|
});
|
|
6326
6326
|
res.on("error", (err) => {
|
|
6327
6327
|
if (resolved) return;
|
|
6328
6328
|
resolved = true;
|
|
6329
6329
|
clearTimeout(timer);
|
|
6330
|
-
reject(new Error(`[EmbeddingClient] POST ${
|
|
6330
|
+
reject(new Error(`[EmbeddingClient] POST ${path21} response error: ${err.message}`));
|
|
6331
6331
|
});
|
|
6332
6332
|
}
|
|
6333
6333
|
);
|
|
@@ -6335,7 +6335,7 @@ var init_embedding_client = __esm({
|
|
|
6335
6335
|
if (resolved) return;
|
|
6336
6336
|
resolved = true;
|
|
6337
6337
|
clearTimeout(timer);
|
|
6338
|
-
reject(new Error(`[EmbeddingClient] POST ${
|
|
6338
|
+
reject(new Error(`[EmbeddingClient] POST ${path21} request error: ${err.message}`));
|
|
6339
6339
|
});
|
|
6340
6340
|
req.write(body);
|
|
6341
6341
|
req.end();
|
|
@@ -7788,9 +7788,9 @@ var init_store_operations = __esm({
|
|
|
7788
7788
|
});
|
|
7789
7789
|
|
|
7790
7790
|
// src/knowledge/store.ts
|
|
7791
|
-
import * as
|
|
7791
|
+
import * as fs14 from "node:fs";
|
|
7792
7792
|
import * as fsPromises from "node:fs/promises";
|
|
7793
|
-
import * as
|
|
7793
|
+
import * as path16 from "node:path";
|
|
7794
7794
|
function isLanceCommitConflict(err) {
|
|
7795
7795
|
const msg2 = err instanceof Error ? err.message : String(err);
|
|
7796
7796
|
return /commit conflict/i.test(msg2) || /too many concurrent writes/i.test(msg2) || msg2.includes("Version mismatch") || msg2.includes("Lock error") || msg2.includes("Commit error");
|
|
@@ -7852,7 +7852,7 @@ var init_store = __esm({
|
|
|
7852
7852
|
constructor(options) {
|
|
7853
7853
|
this.options = options;
|
|
7854
7854
|
this.tableName = this.retrieval === "bm25" ? BM25_TABLE_NAME : "knowledge";
|
|
7855
|
-
this.manifestPath =
|
|
7855
|
+
this.manifestPath = path16.join(this.options.dbDir, "store-manifest.json");
|
|
7856
7856
|
}
|
|
7857
7857
|
/** Normalized retrieval strategy: an unset option means the historical
|
|
7858
7858
|
* 'vector' (hybrid) behavior, not bm25. Every mode check goes through this. */
|
|
@@ -7890,7 +7890,7 @@ var init_store = __esm({
|
|
|
7890
7890
|
}
|
|
7891
7891
|
async loadManifest() {
|
|
7892
7892
|
try {
|
|
7893
|
-
if (
|
|
7893
|
+
if (fs14.existsSync(this.manifestPath)) {
|
|
7894
7894
|
const content = await fsPromises.readFile(this.manifestPath, "utf-8");
|
|
7895
7895
|
const manifest = JSON.parse(content);
|
|
7896
7896
|
const savedVectorTable = typeof manifest.activeTableName === "string" ? manifest.activeTableName : null;
|
|
@@ -7935,7 +7935,7 @@ var init_store = __esm({
|
|
|
7935
7935
|
return true;
|
|
7936
7936
|
} catch (err) {
|
|
7937
7937
|
logger.warn("[store] Failed to save manifest:", err);
|
|
7938
|
-
if (
|
|
7938
|
+
if (fs14.existsSync(tempPath)) {
|
|
7939
7939
|
try {
|
|
7940
7940
|
await fsPromises.unlink(tempPath);
|
|
7941
7941
|
} catch {
|
|
@@ -7967,8 +7967,8 @@ var init_store = __esm({
|
|
|
7967
7967
|
async openInternal() {
|
|
7968
7968
|
if (this.db) return;
|
|
7969
7969
|
try {
|
|
7970
|
-
if (!
|
|
7971
|
-
|
|
7970
|
+
if (!fs14.existsSync(this.options.dbDir)) {
|
|
7971
|
+
fs14.mkdirSync(this.options.dbDir, { recursive: true, mode: 448 });
|
|
7972
7972
|
}
|
|
7973
7973
|
this.db = await (await getLancedb()).connect(this.options.dbDir);
|
|
7974
7974
|
const tableNames = await this.db.tableNames();
|
|
@@ -8091,10 +8091,10 @@ var init_store = __esm({
|
|
|
8091
8091
|
logger.warn(`[store] countRows failed before backup (continuing): ${err instanceof Error ? err.message : String(err)}`);
|
|
8092
8092
|
}
|
|
8093
8093
|
this.table = null;
|
|
8094
|
-
const oldDir =
|
|
8095
|
-
const backupDir =
|
|
8094
|
+
const oldDir = path16.join(this.options.dbDir, `${this.tableName}.lance`);
|
|
8095
|
+
const backupDir = path16.join(this.options.dbDir, `${backupTableName}.lance`);
|
|
8096
8096
|
try {
|
|
8097
|
-
if (
|
|
8097
|
+
if (fs14.existsSync(oldDir)) {
|
|
8098
8098
|
await fsPromises.rename(oldDir, backupDir);
|
|
8099
8099
|
logger.info(`[store] Successfully backed up database directory to ${backupDir}`);
|
|
8100
8100
|
}
|
|
@@ -8184,8 +8184,8 @@ var init_store = __esm({
|
|
|
8184
8184
|
await this.db.dropTable(oldTableName);
|
|
8185
8185
|
logger.info(`[store] Renaming ${tempTableName} to ${canonicalName}...`);
|
|
8186
8186
|
try {
|
|
8187
|
-
const tempDir =
|
|
8188
|
-
const canonicalDir =
|
|
8187
|
+
const tempDir = path16.join(this.options.dbDir, `${tempTableName}.lance`);
|
|
8188
|
+
const canonicalDir = path16.join(this.options.dbDir, `${canonicalName}.lance`);
|
|
8189
8189
|
await fsPromises.rename(tempDir, canonicalDir);
|
|
8190
8190
|
this.tableName = canonicalName;
|
|
8191
8191
|
if (await this.saveManifest()) {
|
|
@@ -8559,9 +8559,9 @@ var init_store = __esm({
|
|
|
8559
8559
|
};
|
|
8560
8560
|
});
|
|
8561
8561
|
try {
|
|
8562
|
-
const dir =
|
|
8563
|
-
await
|
|
8564
|
-
await
|
|
8562
|
+
const dir = path16.dirname(outputPath);
|
|
8563
|
+
await fs14.promises.mkdir(dir, { recursive: true });
|
|
8564
|
+
await fs14.promises.writeFile(outputPath, JSON.stringify(exportData), "utf-8");
|
|
8565
8565
|
logger.info(`[store] Successfully exported ${exportData.length} entries to ${outputPath}`);
|
|
8566
8566
|
} catch (err) {
|
|
8567
8567
|
logger.error(`[store] Failed to export knowledge store for web:`, err);
|
|
@@ -8760,7 +8760,7 @@ var init_store = __esm({
|
|
|
8760
8760
|
else if (name.includes("_migration_")) migrationDirs.push(name);
|
|
8761
8761
|
}
|
|
8762
8762
|
for (const name of migrationDirs) {
|
|
8763
|
-
const fullPath =
|
|
8763
|
+
const fullPath = path16.join(this.options.dbDir, name);
|
|
8764
8764
|
logger.info(`[store] Removing orphaned migration temp directory: ${name}`);
|
|
8765
8765
|
await fsPromises.rm(fullPath, { recursive: true, force: true, maxRetries: 5, retryDelay: 50 });
|
|
8766
8766
|
}
|
|
@@ -8773,7 +8773,7 @@ var init_store = __esm({
|
|
|
8773
8773
|
const backupCutoff = Math.trunc(Date.now() - ttlDays * 24 * 60 * 60 * 1e3);
|
|
8774
8774
|
for (const { name, ts } of ranked.slice(0, -1)) {
|
|
8775
8775
|
if (ts >= backupCutoff) continue;
|
|
8776
|
-
const fullPath =
|
|
8776
|
+
const fullPath = path16.join(this.options.dbDir, name);
|
|
8777
8777
|
logger.info(`[store] Removing superseded backup directory: ${name}`);
|
|
8778
8778
|
await fsPromises.rm(fullPath, { recursive: true, force: true, maxRetries: 5, retryDelay: 50 });
|
|
8779
8779
|
}
|
|
@@ -8796,7 +8796,7 @@ var init_store = __esm({
|
|
|
8796
8796
|
if (!Number.isNaN(parsed)) return parsed;
|
|
8797
8797
|
}
|
|
8798
8798
|
try {
|
|
8799
|
-
const stat6 = await fsPromises.stat(
|
|
8799
|
+
const stat6 = await fsPromises.stat(path16.join(this.options.dbDir, name));
|
|
8800
8800
|
return stat6.mtimeMs;
|
|
8801
8801
|
} catch {
|
|
8802
8802
|
return 0;
|
|
@@ -9310,8 +9310,8 @@ var init_chunker = __esm({
|
|
|
9310
9310
|
});
|
|
9311
9311
|
|
|
9312
9312
|
// src/knowledge/index.ts
|
|
9313
|
-
import * as
|
|
9314
|
-
import * as
|
|
9313
|
+
import * as fs15 from "node:fs";
|
|
9314
|
+
import * as path17 from "node:path";
|
|
9315
9315
|
function getMigrationStrategy(config) {
|
|
9316
9316
|
const strategy = config.MIGRATION_STRATEGY;
|
|
9317
9317
|
if (!strategy) return void 0;
|
|
@@ -9378,12 +9378,12 @@ async function createKnowledgeStoreComponents(embedderFactory, reconnectFactory,
|
|
|
9378
9378
|
}
|
|
9379
9379
|
async function forceDeleteKnowledgeStore(config, workspace) {
|
|
9380
9380
|
const dbDir = getDbDir(config, workspace);
|
|
9381
|
-
if (!
|
|
9381
|
+
if (!fs15.existsSync(dbDir)) return;
|
|
9382
9382
|
let tableName = "knowledge";
|
|
9383
|
-
const manifestPath =
|
|
9383
|
+
const manifestPath = path17.join(dbDir, "store-manifest.json");
|
|
9384
9384
|
try {
|
|
9385
|
-
if (
|
|
9386
|
-
const manifest = JSON.parse(
|
|
9385
|
+
if (fs15.existsSync(manifestPath)) {
|
|
9386
|
+
const manifest = JSON.parse(fs15.readFileSync(manifestPath, "utf-8"));
|
|
9387
9387
|
const key = config?.KNOWLEDGE_STORE_RETRIEVAL === "bm25" ? "activeBm25TableName" : "activeTableName";
|
|
9388
9388
|
const saved = key === "activeBm25TableName" ? manifest.activeBm25TableName : manifest.activeTableName;
|
|
9389
9389
|
if (saved) tableName = saved;
|
|
@@ -9391,10 +9391,10 @@ async function forceDeleteKnowledgeStore(config, workspace) {
|
|
|
9391
9391
|
} catch (err) {
|
|
9392
9392
|
logger.warn(`[knowledge] Failed to read manifest before forced delete, defaulting to table '${tableName}':`, err);
|
|
9393
9393
|
}
|
|
9394
|
-
const tableDir =
|
|
9395
|
-
if (!
|
|
9394
|
+
const tableDir = path17.join(dbDir, `${tableName}.lance`);
|
|
9395
|
+
if (!fs15.existsSync(tableDir)) return;
|
|
9396
9396
|
try {
|
|
9397
|
-
|
|
9397
|
+
fs15.rmSync(tableDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 50 });
|
|
9398
9398
|
logger.info(`[knowledge] Corrupted table directory ${tableDir} forcefully deleted; will be recreated on next init.`);
|
|
9399
9399
|
} catch (err) {
|
|
9400
9400
|
logger.error(`[knowledge] Failed to forcefully delete table directory ${tableDir}:`, err);
|
|
@@ -9424,7 +9424,7 @@ var knowledge_store_service_exports = {};
|
|
|
9424
9424
|
__export(knowledge_store_service_exports, {
|
|
9425
9425
|
KnowledgeStoreService: () => KnowledgeStoreService
|
|
9426
9426
|
});
|
|
9427
|
-
import * as
|
|
9427
|
+
import * as path18 from "node:path";
|
|
9428
9428
|
var KnowledgeStoreService;
|
|
9429
9429
|
var init_knowledge_store_service = __esm({
|
|
9430
9430
|
"src/infrastructure/knowledge-store-service.ts"() {
|
|
@@ -9527,7 +9527,7 @@ var init_knowledge_store_service = __esm({
|
|
|
9527
9527
|
};
|
|
9528
9528
|
const container = tryGetServiceContainerFromCtx(ctx);
|
|
9529
9529
|
const pathConfig = await getService(ServiceNames.STATE_PATH_CONFIGURATION, void 0, container);
|
|
9530
|
-
const lockPath =
|
|
9530
|
+
const lockPath = path18.join(pathConfig.getLockDirPath(), "knowledge-store-init.lock");
|
|
9531
9531
|
if (!this._initLock) {
|
|
9532
9532
|
const processLifecycle = await getService(ServiceNames.PROCESS_LIFECYCLE, void 0, container);
|
|
9533
9533
|
this._initLock = new FileLockService({
|
|
@@ -9538,12 +9538,12 @@ var init_knowledge_store_service = __esm({
|
|
|
9538
9538
|
// liveOwnerStaleThreshold (120s default) that actually governs when a
|
|
9539
9539
|
// provably-alive holder's lock may be physically reclaimed. This lock
|
|
9540
9540
|
// protects a post-run FTS rebuild plus optimize with no fixed duration; if
|
|
9541
|
-
// that holder has a stretch of non-yielding synchronous work
|
|
9542
|
-
// large
|
|
9543
|
-
//
|
|
9544
|
-
// waiting caller would abandon and throw "no sign of
|
|
9545
|
-
// holder" against a holder that was never actually
|
|
9546
|
-
// before the lock would even become reclaim-eligible. Raised to match
|
|
9541
|
+
// that holder has a stretch of non-yielding synchronous work longer than
|
|
9542
|
+
// 20s (a single large native call — the FTS rebuild or optimize — blocks the
|
|
9543
|
+
// event loop for its full duration, during which the heartbeat literally
|
|
9544
|
+
// cannot fire), a waiting caller would abandon and throw "no sign of
|
|
9545
|
+
// progress from its holder" against a holder that was never actually
|
|
9546
|
+
// wedged — up to 100s before the lock would even become reclaim-eligible. Raised to match
|
|
9547
9547
|
// liveOwnerStaleThreshold's own default so a live holder is given the same
|
|
9548
9548
|
// grace period here that the reclaim logic itself already grants it.
|
|
9549
9549
|
lockTimeout: 12e4,
|
|
@@ -10681,20 +10681,20 @@ __export(skill_installer_exports, {
|
|
|
10681
10681
|
uninstallSkill: () => uninstallSkill
|
|
10682
10682
|
});
|
|
10683
10683
|
import * as crypto10 from "node:crypto";
|
|
10684
|
-
import * as
|
|
10685
|
-
import * as
|
|
10686
|
-
import * as
|
|
10684
|
+
import * as fs17 from "node:fs";
|
|
10685
|
+
import * as os7 from "node:os";
|
|
10686
|
+
import * as path19 from "node:path";
|
|
10687
10687
|
import { fileURLToPath as fileURLToPath5 } from "node:url";
|
|
10688
10688
|
function readSkillVersion(sourceDir) {
|
|
10689
10689
|
try {
|
|
10690
|
-
const md =
|
|
10690
|
+
const md = fs17.readFileSync(path19.join(sourceDir, "SKILL.md"), "utf-8");
|
|
10691
10691
|
return /"version"\s*:\s*"([^"]+)"/.exec(md)?.[1] ?? null;
|
|
10692
10692
|
} catch {
|
|
10693
10693
|
return null;
|
|
10694
10694
|
}
|
|
10695
10695
|
}
|
|
10696
10696
|
function homeDir(opts) {
|
|
10697
|
-
return opts?.home ??
|
|
10697
|
+
return opts?.home ?? os7.homedir();
|
|
10698
10698
|
}
|
|
10699
10699
|
function resolveSkillSourceDir(explicit) {
|
|
10700
10700
|
const candidates = [];
|
|
@@ -10702,7 +10702,7 @@ function resolveSkillSourceDir(explicit) {
|
|
|
10702
10702
|
if (process.env["PI_RESEARCH_SKILL_DIR"]) candidates.push(process.env["PI_RESEARCH_SKILL_DIR"]);
|
|
10703
10703
|
const fromHere = (() => {
|
|
10704
10704
|
try {
|
|
10705
|
-
return
|
|
10705
|
+
return path19.dirname(fileURLToPath5(import.meta.url));
|
|
10706
10706
|
} catch {
|
|
10707
10707
|
return void 0;
|
|
10708
10708
|
}
|
|
@@ -10711,29 +10711,29 @@ function resolveSkillSourceDir(explicit) {
|
|
|
10711
10711
|
if (!start) continue;
|
|
10712
10712
|
let dir = start;
|
|
10713
10713
|
for (let i = 0; i < 8; i++) {
|
|
10714
|
-
candidates.push(
|
|
10715
|
-
candidates.push(
|
|
10716
|
-
const parent =
|
|
10714
|
+
candidates.push(path19.join(dir, SKILL_SOURCE_DIR, SKILL_NAME));
|
|
10715
|
+
candidates.push(path19.join(dir, LEGACY_SKILL_SOURCE_DIR, SKILL_NAME));
|
|
10716
|
+
const parent = path19.dirname(dir);
|
|
10717
10717
|
if (parent === dir) break;
|
|
10718
10718
|
dir = parent;
|
|
10719
10719
|
}
|
|
10720
10720
|
}
|
|
10721
10721
|
for (const c of candidates) {
|
|
10722
10722
|
try {
|
|
10723
|
-
if (
|
|
10723
|
+
if (fs17.existsSync(path19.join(c, "SKILL.md"))) return path19.resolve(c);
|
|
10724
10724
|
} catch {
|
|
10725
10725
|
}
|
|
10726
10726
|
}
|
|
10727
10727
|
throw new Error("Could not locate the bundled research skill (agent-skill/pi-research/SKILL.md). Set PI_RESEARCH_SKILL_DIR to its path.");
|
|
10728
10728
|
}
|
|
10729
10729
|
function getManifestPath(opts) {
|
|
10730
|
-
return
|
|
10730
|
+
return path19.join(homeDir(opts), ".pi", "research", "installed-skills.json");
|
|
10731
10731
|
}
|
|
10732
10732
|
function readManifest(opts) {
|
|
10733
10733
|
const p = getManifestPath(opts);
|
|
10734
10734
|
try {
|
|
10735
|
-
if (
|
|
10736
|
-
const parsed = JSON.parse(
|
|
10735
|
+
if (fs17.existsSync(p)) {
|
|
10736
|
+
const parsed = JSON.parse(fs17.readFileSync(p, "utf-8"));
|
|
10737
10737
|
if (parsed && Array.isArray(parsed.entries)) {
|
|
10738
10738
|
return { version: 1, package: PACKAGE_NAME, entries: parsed.entries };
|
|
10739
10739
|
}
|
|
@@ -10744,14 +10744,14 @@ function readManifest(opts) {
|
|
|
10744
10744
|
}
|
|
10745
10745
|
function writeManifest(manifest, opts) {
|
|
10746
10746
|
const p = getManifestPath(opts);
|
|
10747
|
-
|
|
10747
|
+
fs17.mkdirSync(path19.dirname(p), { recursive: true });
|
|
10748
10748
|
const tmp = `${p}.tmp.${process.pid}.${Date.now()}.${manifestTmpSeq++}`;
|
|
10749
|
-
|
|
10749
|
+
fs17.writeFileSync(tmp, JSON.stringify(manifest, null, 2) + "\n", "utf-8");
|
|
10750
10750
|
try {
|
|
10751
|
-
|
|
10751
|
+
fs17.renameSync(tmp, p);
|
|
10752
10752
|
} catch (err) {
|
|
10753
10753
|
try {
|
|
10754
|
-
|
|
10754
|
+
fs17.unlinkSync(tmp);
|
|
10755
10755
|
} catch {
|
|
10756
10756
|
}
|
|
10757
10757
|
throw err;
|
|
@@ -10764,10 +10764,10 @@ function upsertEntry(manifest, entry) {
|
|
|
10764
10764
|
}
|
|
10765
10765
|
function isOwnedSymlink(targetPath, expectedSource) {
|
|
10766
10766
|
try {
|
|
10767
|
-
const st =
|
|
10767
|
+
const st = fs17.lstatSync(targetPath);
|
|
10768
10768
|
if (!st.isSymbolicLink()) return false;
|
|
10769
|
-
const dest =
|
|
10770
|
-
if (expectedSource &&
|
|
10769
|
+
const dest = path19.resolve(path19.dirname(targetPath), fs17.readlinkSync(targetPath));
|
|
10770
|
+
if (expectedSource && path19.resolve(expectedSource) === dest) return true;
|
|
10771
10771
|
return OWNED_SYMLINK_DEST.test(dest);
|
|
10772
10772
|
} catch {
|
|
10773
10773
|
return false;
|
|
@@ -10775,7 +10775,7 @@ function isOwnedSymlink(targetPath, expectedSource) {
|
|
|
10775
10775
|
}
|
|
10776
10776
|
function isOwnedCopy(targetPath) {
|
|
10777
10777
|
try {
|
|
10778
|
-
const md =
|
|
10778
|
+
const md = fs17.readFileSync(path19.join(targetPath, "SKILL.md"), "utf-8");
|
|
10779
10779
|
return md.includes(PACKAGE_NAME);
|
|
10780
10780
|
} catch {
|
|
10781
10781
|
return false;
|
|
@@ -10784,16 +10784,16 @@ function isOwnedCopy(targetPath) {
|
|
|
10784
10784
|
function detectHarnesses(opts) {
|
|
10785
10785
|
const home = homeDir(opts);
|
|
10786
10786
|
return HARNESSES.map((h) => {
|
|
10787
|
-
const absSkillsDir =
|
|
10788
|
-
const absSkillPath =
|
|
10787
|
+
const absSkillsDir = path19.join(home, h.skillsDir);
|
|
10788
|
+
const absSkillPath = path19.join(absSkillsDir, SKILL_NAME);
|
|
10789
10789
|
let present = false;
|
|
10790
10790
|
try {
|
|
10791
|
-
present =
|
|
10791
|
+
present = fs17.existsSync(path19.join(home, h.baseDir));
|
|
10792
10792
|
} catch {
|
|
10793
10793
|
}
|
|
10794
10794
|
let installed = "none";
|
|
10795
10795
|
try {
|
|
10796
|
-
if (
|
|
10796
|
+
if (fs17.existsSync(absSkillPath) || isSymlinkPresent(absSkillPath)) {
|
|
10797
10797
|
if (isOwnedSymlink(absSkillPath)) installed = "owned-symlink";
|
|
10798
10798
|
else if (isOwnedCopy(absSkillPath)) installed = "owned-copy";
|
|
10799
10799
|
else installed = "foreign";
|
|
@@ -10806,14 +10806,14 @@ function detectHarnesses(opts) {
|
|
|
10806
10806
|
}
|
|
10807
10807
|
function isUsableSkillDir(p) {
|
|
10808
10808
|
try {
|
|
10809
|
-
return
|
|
10809
|
+
return fs17.existsSync(path19.join(p, "SKILL.md")) && fs17.existsSync(path19.join(p, "scripts", "run.mjs"));
|
|
10810
10810
|
} catch {
|
|
10811
10811
|
return false;
|
|
10812
10812
|
}
|
|
10813
10813
|
}
|
|
10814
10814
|
function isSymlinkPresent(p) {
|
|
10815
10815
|
try {
|
|
10816
|
-
return
|
|
10816
|
+
return fs17.lstatSync(p).isSymbolicLink();
|
|
10817
10817
|
} catch {
|
|
10818
10818
|
return false;
|
|
10819
10819
|
}
|
|
@@ -10837,22 +10837,22 @@ function installSkill(toolIds, opts = {}) {
|
|
|
10837
10837
|
results.push({ tool: id, path: "", type: "symlink", status: "error", message: `unknown target "${id}"` });
|
|
10838
10838
|
continue;
|
|
10839
10839
|
}
|
|
10840
|
-
const absSkillsDir =
|
|
10841
|
-
const absSkillPath =
|
|
10840
|
+
const absSkillsDir = path19.join(home, def.skillsDir);
|
|
10841
|
+
const absSkillPath = path19.join(absSkillsDir, SKILL_NAME);
|
|
10842
10842
|
const type = opts.copy ? "copy" : "symlink";
|
|
10843
10843
|
if (isOwnedSymlink(absSkillPath, source) || isOwnedCopy(absSkillPath)) {
|
|
10844
10844
|
try {
|
|
10845
|
-
const lst =
|
|
10845
|
+
const lst = fs17.lstatSync(absSkillPath);
|
|
10846
10846
|
if (lst.isSymbolicLink()) {
|
|
10847
|
-
const dest =
|
|
10848
|
-
if (dest !==
|
|
10847
|
+
const dest = path19.resolve(path19.dirname(absSkillPath), fs17.readlinkSync(absSkillPath));
|
|
10848
|
+
if (dest !== path19.resolve(source)) {
|
|
10849
10849
|
if (opts.dryRun) {
|
|
10850
10850
|
results.push({ tool: id, path: absSkillPath, type: "symlink", status: "planned", message: "would re-point stale symlink to current source" });
|
|
10851
10851
|
continue;
|
|
10852
10852
|
}
|
|
10853
|
-
|
|
10853
|
+
fs17.unlinkSync(absSkillPath);
|
|
10854
10854
|
try {
|
|
10855
|
-
|
|
10855
|
+
fs17.symlinkSync(source, absSkillPath, process.platform === "win32" ? "junction" : "dir");
|
|
10856
10856
|
upsertEntry(manifest, { tool: id, path: absSkillPath, type: "symlink", source, createdAt: now, ...skillVersion ? { version: skillVersion } : {} });
|
|
10857
10857
|
results.push({ tool: id, path: absSkillPath, type: "symlink", status: "installed", message: "re-pointed stale symlink to current source" });
|
|
10858
10858
|
} catch (relinkErr) {
|
|
@@ -10878,7 +10878,7 @@ function installSkill(toolIds, opts = {}) {
|
|
|
10878
10878
|
}
|
|
10879
10879
|
continue;
|
|
10880
10880
|
}
|
|
10881
|
-
if (
|
|
10881
|
+
if (fs17.existsSync(absSkillPath) || isSymlinkPresent(absSkillPath)) {
|
|
10882
10882
|
results.push({ tool: id, path: absSkillPath, type, status: "skipped-foreign", message: 'a non-pi-research "research" skill already exists here; left untouched' });
|
|
10883
10883
|
continue;
|
|
10884
10884
|
}
|
|
@@ -10887,10 +10887,10 @@ function installSkill(toolIds, opts = {}) {
|
|
|
10887
10887
|
continue;
|
|
10888
10888
|
}
|
|
10889
10889
|
try {
|
|
10890
|
-
|
|
10890
|
+
fs17.mkdirSync(absSkillsDir, { recursive: true });
|
|
10891
10891
|
if (type === "symlink") {
|
|
10892
10892
|
try {
|
|
10893
|
-
|
|
10893
|
+
fs17.symlinkSync(source, absSkillPath, process.platform === "win32" ? "junction" : "dir");
|
|
10894
10894
|
} catch (err) {
|
|
10895
10895
|
copyDir(source, absSkillPath);
|
|
10896
10896
|
upsertEntry(manifest, { tool: id, path: absSkillPath, type: "copy", source, createdAt: now, ...skillVersion ? { version: skillVersion } : {} });
|
|
@@ -10927,7 +10927,7 @@ function uninstallSkill(toolIds, opts = {}) {
|
|
|
10927
10927
|
continue;
|
|
10928
10928
|
}
|
|
10929
10929
|
const owned = isOwnedSymlink(skillPath) || isOwnedCopy(skillPath);
|
|
10930
|
-
const present =
|
|
10930
|
+
const present = fs17.existsSync(skillPath) || isSymlinkPresent(skillPath);
|
|
10931
10931
|
if (!present) {
|
|
10932
10932
|
results.push({ tool, path: skillPath, status: "not-present" });
|
|
10933
10933
|
continue;
|
|
@@ -10945,8 +10945,8 @@ function uninstallSkill(toolIds, opts = {}) {
|
|
|
10945
10945
|
continue;
|
|
10946
10946
|
}
|
|
10947
10947
|
try {
|
|
10948
|
-
if (isSymlinkPresent(skillPath))
|
|
10949
|
-
else
|
|
10948
|
+
if (isSymlinkPresent(skillPath)) fs17.unlinkSync(skillPath);
|
|
10949
|
+
else fs17.rmSync(skillPath, { recursive: true, force: true, maxRetries: 5, retryDelay: 50 });
|
|
10950
10950
|
results.push({ tool, path: skillPath, status: "removed" });
|
|
10951
10951
|
} catch (err) {
|
|
10952
10952
|
results.push({ tool, path: skillPath, status: "error", message: err?.message ?? String(err) });
|
|
@@ -10959,7 +10959,7 @@ function uninstallSkill(toolIds, opts = {}) {
|
|
|
10959
10959
|
writeManifest({ version: 1, package: PACKAGE_NAME, entries: keep }, opts);
|
|
10960
10960
|
} else {
|
|
10961
10961
|
try {
|
|
10962
|
-
|
|
10962
|
+
fs17.rmSync(getManifestPath(opts));
|
|
10963
10963
|
} catch {
|
|
10964
10964
|
}
|
|
10965
10965
|
}
|
|
@@ -10981,56 +10981,56 @@ function reconcileSkillInstalls(opts = {}) {
|
|
|
10981
10981
|
for (const e of manifest.entries) {
|
|
10982
10982
|
if (e.type !== "symlink") {
|
|
10983
10983
|
const stale = currentVersion !== null && e.version !== currentVersion;
|
|
10984
|
-
if (stale && source !== null &&
|
|
10985
|
-
const dir2 =
|
|
10986
|
-
const base2 =
|
|
10984
|
+
if (stale && source !== null && fs17.existsSync(e.path) && isOwnedCopy(e.path)) {
|
|
10985
|
+
const dir2 = path19.dirname(e.path);
|
|
10986
|
+
const base2 = path19.basename(e.path);
|
|
10987
10987
|
try {
|
|
10988
|
-
for (const name of
|
|
10988
|
+
for (const name of fs17.readdirSync(dir2)) {
|
|
10989
10989
|
if (name.startsWith(`.${base2}.new-`) || name.startsWith(`.${base2}.old-`)) {
|
|
10990
|
-
|
|
10990
|
+
fs17.rmSync(path19.join(dir2, name), { recursive: true, force: true });
|
|
10991
10991
|
}
|
|
10992
10992
|
}
|
|
10993
10993
|
} catch {
|
|
10994
10994
|
}
|
|
10995
10995
|
const suffix = crypto10.randomBytes(4).toString("hex");
|
|
10996
|
-
const fresh =
|
|
10997
|
-
const retired =
|
|
10996
|
+
const fresh = path19.join(dir2, `.${base2}.new-${suffix}`);
|
|
10997
|
+
const retired = path19.join(dir2, `.${base2}.old-${suffix}`);
|
|
10998
10998
|
try {
|
|
10999
10999
|
copyDir(source, fresh);
|
|
11000
11000
|
} catch {
|
|
11001
11001
|
try {
|
|
11002
|
-
|
|
11002
|
+
fs17.rmSync(fresh, { recursive: true, force: true });
|
|
11003
11003
|
} catch {
|
|
11004
11004
|
}
|
|
11005
11005
|
keep.push(e);
|
|
11006
11006
|
continue;
|
|
11007
11007
|
}
|
|
11008
11008
|
try {
|
|
11009
|
-
|
|
11009
|
+
fs17.renameSync(e.path, retired);
|
|
11010
11010
|
} catch {
|
|
11011
11011
|
try {
|
|
11012
|
-
|
|
11012
|
+
fs17.rmSync(fresh, { recursive: true, force: true });
|
|
11013
11013
|
} catch {
|
|
11014
11014
|
}
|
|
11015
11015
|
keep.push(e);
|
|
11016
11016
|
continue;
|
|
11017
11017
|
}
|
|
11018
11018
|
try {
|
|
11019
|
-
|
|
11019
|
+
fs17.renameSync(fresh, e.path);
|
|
11020
11020
|
} catch {
|
|
11021
11021
|
try {
|
|
11022
|
-
|
|
11022
|
+
fs17.renameSync(retired, e.path);
|
|
11023
11023
|
} catch {
|
|
11024
11024
|
}
|
|
11025
11025
|
try {
|
|
11026
|
-
|
|
11026
|
+
fs17.rmSync(fresh, { recursive: true, force: true });
|
|
11027
11027
|
} catch {
|
|
11028
11028
|
}
|
|
11029
11029
|
keep.push(e);
|
|
11030
11030
|
continue;
|
|
11031
11031
|
}
|
|
11032
11032
|
try {
|
|
11033
|
-
|
|
11033
|
+
fs17.rmSync(retired, { recursive: true, force: true, maxRetries: 5, retryDelay: 50 });
|
|
11034
11034
|
} catch {
|
|
11035
11035
|
}
|
|
11036
11036
|
keep.push({ ...e, source, version: currentVersion });
|
|
@@ -11040,12 +11040,12 @@ function reconcileSkillInstalls(opts = {}) {
|
|
|
11040
11040
|
}
|
|
11041
11041
|
continue;
|
|
11042
11042
|
}
|
|
11043
|
-
const dir =
|
|
11044
|
-
const base =
|
|
11043
|
+
const dir = path19.dirname(e.path);
|
|
11044
|
+
const base = path19.basename(e.path);
|
|
11045
11045
|
try {
|
|
11046
|
-
for (const name of
|
|
11046
|
+
for (const name of fs17.readdirSync(dir)) {
|
|
11047
11047
|
if (name.startsWith(`.${base}.new-`)) {
|
|
11048
|
-
|
|
11048
|
+
fs17.rmSync(path19.join(dir, name), { recursive: true, force: true });
|
|
11049
11049
|
}
|
|
11050
11050
|
}
|
|
11051
11051
|
} catch {
|
|
@@ -11058,30 +11058,30 @@ function reconcileSkillInstalls(opts = {}) {
|
|
|
11058
11058
|
keep.push(e);
|
|
11059
11059
|
continue;
|
|
11060
11060
|
}
|
|
11061
|
-
const targetLive =
|
|
11061
|
+
const targetLive = fs17.existsSync(e.path) && isUsableSkillDir(e.path);
|
|
11062
11062
|
if (!targetLive) {
|
|
11063
11063
|
if (source !== null) {
|
|
11064
11064
|
try {
|
|
11065
|
-
|
|
11065
|
+
fs17.unlinkSync(e.path);
|
|
11066
11066
|
} catch {
|
|
11067
11067
|
keep.push(e);
|
|
11068
11068
|
continue;
|
|
11069
11069
|
}
|
|
11070
11070
|
try {
|
|
11071
|
-
|
|
11071
|
+
fs17.symlinkSync(source, e.path, process.platform === "win32" ? "junction" : "dir");
|
|
11072
11072
|
keep.push({ ...e, type: "symlink", source });
|
|
11073
11073
|
result.repointed.push(e.path);
|
|
11074
11074
|
} catch {
|
|
11075
11075
|
const suffix = crypto10.randomBytes(4).toString("hex");
|
|
11076
|
-
const fresh =
|
|
11076
|
+
const fresh = path19.join(dir, `.${base}.new-${suffix}`);
|
|
11077
11077
|
try {
|
|
11078
11078
|
copyDir(source, fresh);
|
|
11079
|
-
|
|
11079
|
+
fs17.renameSync(fresh, e.path);
|
|
11080
11080
|
keep.push({ ...e, type: "copy", source });
|
|
11081
11081
|
result.repointed.push(e.path);
|
|
11082
11082
|
} catch {
|
|
11083
11083
|
try {
|
|
11084
|
-
|
|
11084
|
+
fs17.rmSync(fresh, { recursive: true, force: true });
|
|
11085
11085
|
} catch {
|
|
11086
11086
|
}
|
|
11087
11087
|
result.pruned.push(e.path);
|
|
@@ -11089,7 +11089,7 @@ function reconcileSkillInstalls(opts = {}) {
|
|
|
11089
11089
|
}
|
|
11090
11090
|
} else if (!targetLive) {
|
|
11091
11091
|
try {
|
|
11092
|
-
|
|
11092
|
+
fs17.unlinkSync(e.path);
|
|
11093
11093
|
result.pruned.push(e.path);
|
|
11094
11094
|
} catch {
|
|
11095
11095
|
keep.push(e);
|
|
@@ -11104,21 +11104,21 @@ function reconcileSkillInstalls(opts = {}) {
|
|
|
11104
11104
|
if (keep.length > 0) writeManifest({ version: 1, package: PACKAGE_NAME, entries: keep }, opts);
|
|
11105
11105
|
else {
|
|
11106
11106
|
try {
|
|
11107
|
-
|
|
11107
|
+
fs17.rmSync(getManifestPath(opts));
|
|
11108
11108
|
} catch {
|
|
11109
11109
|
}
|
|
11110
11110
|
}
|
|
11111
11111
|
return result;
|
|
11112
11112
|
}
|
|
11113
11113
|
function copyDir(src, dest) {
|
|
11114
|
-
const st =
|
|
11114
|
+
const st = fs17.statSync(src);
|
|
11115
11115
|
if (st.isDirectory()) {
|
|
11116
|
-
|
|
11117
|
-
for (const name of
|
|
11118
|
-
copyDir(
|
|
11116
|
+
fs17.mkdirSync(dest, { recursive: true });
|
|
11117
|
+
for (const name of fs17.readdirSync(src)) {
|
|
11118
|
+
copyDir(path19.join(src, name), path19.join(dest, name));
|
|
11119
11119
|
}
|
|
11120
11120
|
} else {
|
|
11121
|
-
|
|
11121
|
+
fs17.copyFileSync(src, dest);
|
|
11122
11122
|
}
|
|
11123
11123
|
}
|
|
11124
11124
|
var HARNESSES, SKILL_NAME, PACKAGE_NAME, SKILL_SOURCE_DIR, LEGACY_SKILL_SOURCE_DIR, OWNED_SYMLINK_DEST, SKILL_AGENT_TARGETS, manifestTmpSeq, isTarget;
|
|
@@ -11126,15 +11126,15 @@ var init_skill_installer = __esm({
|
|
|
11126
11126
|
"src/skill-install/skill-installer.ts"() {
|
|
11127
11127
|
"use strict";
|
|
11128
11128
|
HARNESSES = [
|
|
11129
|
-
{ id: "claude", label: "Claude", baseDir: ".claude", skillsDir:
|
|
11130
|
-
{ id: "openclaw", label: "OpenClaw", baseDir: ".openclaw", skillsDir:
|
|
11129
|
+
{ id: "claude", label: "Claude", baseDir: ".claude", skillsDir: path19.join(".claude", "skills"), confidence: "confirmed" },
|
|
11130
|
+
{ id: "openclaw", label: "OpenClaw", baseDir: ".openclaw", skillsDir: path19.join(".openclaw", "skills"), confidence: "confirmed", note: "OpenClaw reads ~/.openclaw/skills as a managed skill root and accepts symlinked skill folders (docs.openclaw.ai/tools/skills)." },
|
|
11131
11131
|
// pi's own skill root is under its agent dir (~/.pi/agent/skills), NOT ~/.pi/skills.
|
|
11132
11132
|
// Listed for DETECTION only — pi is never an install target (see SKILL_AGENT_TARGETS):
|
|
11133
11133
|
// the host already exposes research as a native tool, so a skill there would just
|
|
11134
11134
|
// duplicate it through a slower subprocess.
|
|
11135
|
-
{ id: "pi", label: "pi", baseDir: ".pi", skillsDir:
|
|
11136
|
-
{ id: "codex", label: "OpenAI Codex CLI", baseDir: ".codex", skillsDir:
|
|
11137
|
-
{ id: "agents", label: "Cross-tool (~/.agents/skills)", baseDir: ".agents", skillsDir:
|
|
11135
|
+
{ id: "pi", label: "pi", baseDir: ".pi", skillsDir: path19.join(".pi", "agent", "skills"), confidence: "confirmed" },
|
|
11136
|
+
{ id: "codex", label: "OpenAI Codex CLI", baseDir: ".codex", skillsDir: path19.join(".codex", "skills"), confidence: "unverified", note: "Codex skills support is emerging; path not confirmed by official docs." },
|
|
11137
|
+
{ id: "agents", label: "Cross-tool (~/.agents/skills)", baseDir: ".agents", skillsDir: path19.join(".agents", "skills"), confidence: "unverified", note: "Proposed Universal Agents convention; not yet a ratified standard." }
|
|
11138
11138
|
];
|
|
11139
11139
|
SKILL_NAME = "pi-research";
|
|
11140
11140
|
PACKAGE_NAME = "@lincoln504/pi-research";
|
|
@@ -11150,9 +11150,9 @@ var init_skill_installer = __esm({
|
|
|
11150
11150
|
});
|
|
11151
11151
|
|
|
11152
11152
|
// src/cli.ts
|
|
11153
|
-
import * as
|
|
11154
|
-
import * as
|
|
11155
|
-
import * as
|
|
11153
|
+
import * as fs18 from "node:fs";
|
|
11154
|
+
import * as os8 from "node:os";
|
|
11155
|
+
import * as path20 from "node:path";
|
|
11156
11156
|
import { spawnSync } from "node:child_process";
|
|
11157
11157
|
import { fileURLToPath as fileURLToPath6 } from "node:url";
|
|
11158
11158
|
|
|
@@ -12911,47 +12911,315 @@ init_service_interfaces();
|
|
|
12911
12911
|
init_file_lock_service();
|
|
12912
12912
|
import * as crypto6 from "node:crypto";
|
|
12913
12913
|
import * as net2 from "node:net";
|
|
12914
|
-
import * as
|
|
12914
|
+
import * as path11 from "node:path";
|
|
12915
12915
|
|
|
12916
12916
|
// src/infrastructure/browser/config.ts
|
|
12917
|
-
init_config();
|
|
12918
|
-
init_logger();
|
|
12919
12917
|
import * as crypto4 from "node:crypto";
|
|
12920
|
-
import { join as
|
|
12921
|
-
import { existsSync as
|
|
12922
|
-
import { platform, homedir as
|
|
12918
|
+
import { join as join9 } from "node:path";
|
|
12919
|
+
import { existsSync as existsSync7, mkdirSync as mkdirSync3 } from "node:fs";
|
|
12920
|
+
import { platform, homedir as homedir4 } from "node:os";
|
|
12921
|
+
|
|
12922
|
+
// src/infrastructure/browser/camoufox-layout.ts
|
|
12923
|
+
import * as fs8 from "node:fs";
|
|
12924
|
+
import * as os4 from "node:os";
|
|
12925
|
+
import * as path10 from "node:path";
|
|
12923
12926
|
import { createRequire as createRequire4 } from "node:module";
|
|
12924
|
-
|
|
12925
|
-
|
|
12927
|
+
var COMPAT_FLAG = ".0.5_FLAG";
|
|
12928
|
+
var LEGACY_MARKERS = ["camoufox-bin", "camoufox", "camoufox.exe", "application.ini", "Camoufox.app", "properties.json"];
|
|
12929
|
+
function isDir(p) {
|
|
12930
|
+
try {
|
|
12931
|
+
return fs8.statSync(p).isDirectory();
|
|
12932
|
+
} catch {
|
|
12933
|
+
return false;
|
|
12934
|
+
}
|
|
12935
|
+
}
|
|
12936
|
+
function isFile(p) {
|
|
12937
|
+
try {
|
|
12938
|
+
return fs8.statSync(p).isFile();
|
|
12939
|
+
} catch {
|
|
12940
|
+
return false;
|
|
12941
|
+
}
|
|
12926
12942
|
}
|
|
12927
|
-
function
|
|
12928
|
-
return
|
|
12943
|
+
function customDir(env = process.env) {
|
|
12944
|
+
return env["CAMOUFOX_INSTALL_DIR"] || env["PLAYWRIGHT_BROWSERS_PATH"] || null;
|
|
12929
12945
|
}
|
|
12930
|
-
function
|
|
12931
|
-
|
|
12932
|
-
|
|
12933
|
-
return
|
|
12946
|
+
function defaultInstallDir(env = process.env, platform5 = process.platform, home = os4.homedir()) {
|
|
12947
|
+
if (platform5 === "win32") {
|
|
12948
|
+
const base = env["LOCALAPPDATA"] && path10.win32.isAbsolute(env["LOCALAPPDATA"]) ? env["LOCALAPPDATA"] : path10.join(home, "AppData", "Local");
|
|
12949
|
+
return path10.join(base, "camoufox", "camoufox", "Cache");
|
|
12934
12950
|
}
|
|
12935
|
-
|
|
12936
|
-
|
|
12937
|
-
|
|
12938
|
-
|
|
12939
|
-
|
|
12951
|
+
if (platform5 === "darwin") return path10.join(home, "Library", "Caches", "camoufox");
|
|
12952
|
+
const xdg = (env["XDG_CACHE_HOME"] || "").trim();
|
|
12953
|
+
return path10.join(xdg ? xdg : path10.join(home, ".cache"), "camoufox");
|
|
12954
|
+
}
|
|
12955
|
+
function resolveInstall(env = process.env, platform5 = process.platform, home = os4.homedir()) {
|
|
12956
|
+
const custom = customDir(env);
|
|
12957
|
+
if (!custom) return { dir: defaultInstallDir(env, platform5, home), overrides: {}, custom: null, ignoredCustom: false };
|
|
12958
|
+
if (platform5 === "darwin") {
|
|
12959
|
+
return { dir: defaultInstallDir(env, platform5, home), overrides: {}, custom, ignoredCustom: true };
|
|
12960
|
+
}
|
|
12961
|
+
const resolved = (platform5 === "win32" ? path10.win32 : path10).resolve(custom);
|
|
12962
|
+
if (platform5 === "win32") {
|
|
12963
|
+
const overrides2 = { LOCALAPPDATA: resolved };
|
|
12964
|
+
return { dir: defaultInstallDir({ ...env, ...overrides2 }, platform5, home), overrides: overrides2, custom, ignoredCustom: false };
|
|
12965
|
+
}
|
|
12966
|
+
const endsInCamoufox = path10.basename(resolved).toLowerCase() === "camoufox";
|
|
12967
|
+
const cacheHome = endsInCamoufox ? path10.dirname(resolved) : resolved;
|
|
12968
|
+
const overrides = { XDG_CACHE_HOME: cacheHome };
|
|
12969
|
+
return { dir: defaultInstallDir({ ...env, ...overrides }, platform5, home), overrides, custom, ignoredCustom: false };
|
|
12970
|
+
}
|
|
12971
|
+
function listInstalls(dir) {
|
|
12972
|
+
const out = [];
|
|
12973
|
+
const browsers = path10.join(dir, "browsers");
|
|
12974
|
+
let repos;
|
|
12975
|
+
try {
|
|
12976
|
+
repos = fs8.readdirSync(browsers, { withFileTypes: true });
|
|
12977
|
+
} catch {
|
|
12978
|
+
return out;
|
|
12979
|
+
}
|
|
12980
|
+
for (const repo of repos) {
|
|
12981
|
+
if (!repo.isDirectory() || repo.name.startsWith(".")) continue;
|
|
12982
|
+
const repoDir = path10.join(browsers, repo.name);
|
|
12983
|
+
let versions;
|
|
12984
|
+
try {
|
|
12985
|
+
versions = fs8.readdirSync(repoDir, { withFileTypes: true });
|
|
12986
|
+
} catch {
|
|
12987
|
+
continue;
|
|
12988
|
+
}
|
|
12989
|
+
for (const v of versions) {
|
|
12990
|
+
if (!v.isDirectory()) continue;
|
|
12991
|
+
const vdir = path10.join(repoDir, v.name);
|
|
12992
|
+
let meta;
|
|
12993
|
+
try {
|
|
12994
|
+
meta = JSON.parse(fs8.readFileSync(path10.join(vdir, "version.json"), "utf8"));
|
|
12995
|
+
} catch {
|
|
12996
|
+
continue;
|
|
12997
|
+
}
|
|
12998
|
+
out.push({
|
|
12999
|
+
repo: repo.name,
|
|
13000
|
+
name: v.name,
|
|
13001
|
+
path: vdir,
|
|
13002
|
+
rel: `browsers/${repo.name}/${v.name}`,
|
|
13003
|
+
version: meta && typeof meta.version === "string" ? meta.version : "",
|
|
13004
|
+
build: meta && typeof meta.build === "string" ? meta.build : ""
|
|
13005
|
+
});
|
|
13006
|
+
}
|
|
13007
|
+
}
|
|
13008
|
+
return out;
|
|
13009
|
+
}
|
|
13010
|
+
function hasInstall(dir) {
|
|
13011
|
+
return listInstalls(dir).length > 0;
|
|
13012
|
+
}
|
|
13013
|
+
function isLegacyFlat(dir) {
|
|
13014
|
+
if (!isDir(dir) || isFile(path10.join(dir, COMPAT_FLAG))) return false;
|
|
13015
|
+
let meta;
|
|
13016
|
+
try {
|
|
13017
|
+
meta = JSON.parse(fs8.readFileSync(path10.join(dir, "version.json"), "utf8"));
|
|
13018
|
+
} catch {
|
|
13019
|
+
return false;
|
|
13020
|
+
}
|
|
13021
|
+
if (!meta || typeof meta.version !== "string" || typeof meta.release !== "string") return false;
|
|
13022
|
+
return LEGACY_MARKERS.some((m) => fs8.existsSync(path10.join(dir, m)));
|
|
13023
|
+
}
|
|
13024
|
+
function hasExplicitChoice(dir) {
|
|
13025
|
+
try {
|
|
13026
|
+
const cfg = JSON.parse(fs8.readFileSync(path10.join(dir, "config.json"), "utf8"));
|
|
13027
|
+
return Boolean(cfg && (cfg.pinned || cfg.channel));
|
|
13028
|
+
} catch {
|
|
13029
|
+
return false;
|
|
13030
|
+
}
|
|
13031
|
+
}
|
|
13032
|
+
function loadPin(packageRoot) {
|
|
13033
|
+
try {
|
|
13034
|
+
const data = JSON.parse(fs8.readFileSync(path10.join(packageRoot, "dist", "data-files", "browser-pin.json"), "utf8"));
|
|
13035
|
+
if (!data || !data.tag) return null;
|
|
13036
|
+
return { repoName: String(data.repo_name).toLowerCase(), version: String(data.version), build: String(data.build) };
|
|
13037
|
+
} catch {
|
|
13038
|
+
return null;
|
|
13039
|
+
}
|
|
13040
|
+
}
|
|
13041
|
+
function findPackageRoot(fromUrl) {
|
|
13042
|
+
try {
|
|
13043
|
+
const require2 = createRequire4(fromUrl);
|
|
13044
|
+
return path10.dirname(require2.resolve("@camoufox/camoufox/package.json"));
|
|
13045
|
+
} catch {
|
|
13046
|
+
return null;
|
|
13047
|
+
}
|
|
13048
|
+
}
|
|
13049
|
+
function matchesPin(build, pin) {
|
|
13050
|
+
return build.repo.toLowerCase() === pin.repoName && build.version === pin.version && build.build === pin.build;
|
|
13051
|
+
}
|
|
13052
|
+
function pruneSuperseded(dir, log = () => {
|
|
13053
|
+
}, pin = null) {
|
|
13054
|
+
if (hasExplicitChoice(dir)) return [];
|
|
13055
|
+
const installs = listInstalls(dir);
|
|
13056
|
+
let keep = null;
|
|
13057
|
+
if (pin) {
|
|
13058
|
+
const paired = installs.find((i) => matchesPin(i, pin));
|
|
13059
|
+
keep = paired ? paired.rel : null;
|
|
12940
13060
|
} else {
|
|
12941
|
-
|
|
12942
|
-
|
|
13061
|
+
try {
|
|
13062
|
+
keep = JSON.parse(fs8.readFileSync(path10.join(dir, "config.json"), "utf8")).active_version || null;
|
|
13063
|
+
} catch {
|
|
13064
|
+
return [];
|
|
13065
|
+
}
|
|
13066
|
+
}
|
|
13067
|
+
if (!keep || !installs.some((i) => i.rel === keep)) return [];
|
|
13068
|
+
const removed = [];
|
|
13069
|
+
for (const i of installs) {
|
|
13070
|
+
if (i.rel === keep) continue;
|
|
13071
|
+
try {
|
|
13072
|
+
fs8.rmSync(i.path, { recursive: true, force: true });
|
|
13073
|
+
removed.push(i.rel);
|
|
13074
|
+
log(`pi-research: removed superseded Camoufox build ${i.rel}`);
|
|
13075
|
+
} catch (e) {
|
|
13076
|
+
log(`pi-research: could not remove superseded Camoufox build ${i.rel}: ${e instanceof Error ? e.message : String(e)}`);
|
|
13077
|
+
}
|
|
13078
|
+
}
|
|
13079
|
+
try {
|
|
13080
|
+
for (const r of fs8.readdirSync(path10.join(dir, "browsers"))) {
|
|
13081
|
+
const rd = path10.join(dir, "browsers", r);
|
|
13082
|
+
if (isDir(rd) && fs8.readdirSync(rd).length === 0) fs8.rmdirSync(rd);
|
|
13083
|
+
}
|
|
13084
|
+
} catch {
|
|
13085
|
+
}
|
|
13086
|
+
return removed;
|
|
13087
|
+
}
|
|
13088
|
+
function stashLegacy(dir) {
|
|
13089
|
+
if (!isLegacyFlat(dir)) return null;
|
|
13090
|
+
const stash = `${dir}.legacy-${process.pid}-${Date.now()}`;
|
|
13091
|
+
fs8.renameSync(dir, stash);
|
|
13092
|
+
return stash;
|
|
13093
|
+
}
|
|
13094
|
+
function restoreLegacy(dir, stash) {
|
|
13095
|
+
if (!stash || !fs8.existsSync(stash)) return;
|
|
13096
|
+
try {
|
|
13097
|
+
fs8.rmSync(dir, { recursive: true, force: true });
|
|
13098
|
+
} catch {
|
|
13099
|
+
}
|
|
13100
|
+
fs8.renameSync(stash, dir);
|
|
13101
|
+
}
|
|
13102
|
+
function discardLegacy(stash) {
|
|
13103
|
+
if (!stash) return;
|
|
13104
|
+
fs8.rmSync(stash, { recursive: true, force: true });
|
|
13105
|
+
}
|
|
13106
|
+
function sweepStaleStashes(dir, log = () => {
|
|
13107
|
+
}) {
|
|
13108
|
+
if (!hasInstall(dir)) return [];
|
|
13109
|
+
const parent = path10.dirname(dir);
|
|
13110
|
+
const prefix = `${path10.basename(dir)}.legacy-`;
|
|
13111
|
+
const removed = [];
|
|
13112
|
+
let entries;
|
|
13113
|
+
try {
|
|
13114
|
+
entries = fs8.readdirSync(parent);
|
|
13115
|
+
} catch {
|
|
13116
|
+
return removed;
|
|
13117
|
+
}
|
|
13118
|
+
for (const e of entries) {
|
|
13119
|
+
if (!e.startsWith(prefix)) continue;
|
|
13120
|
+
const p = path10.join(parent, e);
|
|
13121
|
+
if (!isLegacyFlat(p)) continue;
|
|
13122
|
+
try {
|
|
13123
|
+
fs8.rmSync(p, { recursive: true, force: true });
|
|
13124
|
+
removed.push(p);
|
|
13125
|
+
log(`pi-research: removed leftover legacy Camoufox stash ${p}`);
|
|
13126
|
+
} catch {
|
|
13127
|
+
}
|
|
13128
|
+
}
|
|
13129
|
+
return removed;
|
|
13130
|
+
}
|
|
13131
|
+
function isUsable(dir, pin = null) {
|
|
13132
|
+
if (!isFile(path10.join(dir, COMPAT_FLAG))) return false;
|
|
13133
|
+
const installs = listInstalls(dir);
|
|
13134
|
+
if (installs.length === 0) return false;
|
|
13135
|
+
if (pin && !hasExplicitChoice(dir)) return installs.some((i) => matchesPin(i, pin));
|
|
13136
|
+
return true;
|
|
13137
|
+
}
|
|
13138
|
+
function listStashes(dir) {
|
|
13139
|
+
const parent = path10.dirname(dir);
|
|
13140
|
+
const prefix = `${path10.basename(dir)}.legacy-`;
|
|
13141
|
+
let entries;
|
|
13142
|
+
try {
|
|
13143
|
+
entries = fs8.readdirSync(parent);
|
|
13144
|
+
} catch {
|
|
13145
|
+
return [];
|
|
13146
|
+
}
|
|
13147
|
+
return entries.filter((e) => e.startsWith(prefix)).map((e) => path10.join(parent, e)).filter((p) => isLegacyFlat(p)).sort().reverse();
|
|
13148
|
+
}
|
|
13149
|
+
function recoverStash(dir, log = () => {
|
|
13150
|
+
}) {
|
|
13151
|
+
let empty = !fs8.existsSync(dir);
|
|
13152
|
+
if (!empty) {
|
|
13153
|
+
try {
|
|
13154
|
+
empty = isDir(dir) && fs8.readdirSync(dir).length === 0;
|
|
13155
|
+
} catch {
|
|
13156
|
+
return null;
|
|
13157
|
+
}
|
|
13158
|
+
}
|
|
13159
|
+
if (!empty) return null;
|
|
13160
|
+
const stash = listStashes(dir)[0];
|
|
13161
|
+
if (!stash) return null;
|
|
13162
|
+
try {
|
|
13163
|
+
if (fs8.existsSync(dir)) fs8.rmdirSync(dir);
|
|
13164
|
+
fs8.renameSync(stash, dir);
|
|
13165
|
+
log(`pi-research: restored the previous Camoufox install from ${stash}`);
|
|
13166
|
+
return stash;
|
|
13167
|
+
} catch {
|
|
13168
|
+
return null;
|
|
12943
13169
|
}
|
|
12944
13170
|
}
|
|
13171
|
+
async function provisionBrowser(dir, fetch2, log = () => {
|
|
13172
|
+
}, pin = null) {
|
|
13173
|
+
recoverStash(dir, log);
|
|
13174
|
+
if (isUsable(dir, pin)) {
|
|
13175
|
+
sweepStaleStashes(dir, log);
|
|
13176
|
+
return { status: "present", pruned: pruneSuperseded(dir, log, pin) };
|
|
13177
|
+
}
|
|
13178
|
+
let stash;
|
|
13179
|
+
try {
|
|
13180
|
+
stash = stashLegacy(dir);
|
|
13181
|
+
} catch (e) {
|
|
13182
|
+
return { status: "failed", pruned: [], reason: `could not move the existing legacy install aside (${e instanceof Error ? e.message : String(e)}); left untouched` };
|
|
13183
|
+
}
|
|
13184
|
+
if (stash) log(`pi-research: legacy Camoufox install moved aside to ${stash} until the new browser is verified`);
|
|
13185
|
+
let ok = false;
|
|
13186
|
+
let reason;
|
|
13187
|
+
try {
|
|
13188
|
+
ok = await fetch2() === true;
|
|
13189
|
+
if (!ok) reason = "camoufox fetch did not exit 0";
|
|
13190
|
+
} catch (e) {
|
|
13191
|
+
reason = e instanceof Error ? e.message : String(e);
|
|
13192
|
+
}
|
|
13193
|
+
if (isUsable(dir, pin)) {
|
|
13194
|
+
discardLegacy(stash);
|
|
13195
|
+
if (stash) log("pi-research: removed the legacy Camoufox install (replaced by the new build)");
|
|
13196
|
+
if (!ok) log(`pi-research: camoufox fetch reported a failure after the browser was installed (${reason}); keeping it. Run "npx camoufox fetch" to retry the companion download (e.g. the geoip database).`);
|
|
13197
|
+
sweepStaleStashes(dir, log);
|
|
13198
|
+
return { status: "installed", pruned: pruneSuperseded(dir, log, pin) };
|
|
13199
|
+
}
|
|
13200
|
+
if (ok) reason = "camoufox fetch exited 0 but no usable build was found";
|
|
13201
|
+
restoreLegacy(dir, stash);
|
|
13202
|
+
if (stash) log("pi-research: restored the previous Camoufox install after a failed upgrade");
|
|
13203
|
+
return { status: "failed", pruned: [], reason };
|
|
13204
|
+
}
|
|
13205
|
+
|
|
13206
|
+
// src/infrastructure/browser/config.ts
|
|
13207
|
+
init_config();
|
|
13208
|
+
init_logger();
|
|
13209
|
+
import { createRequire as createRequire5 } from "node:module";
|
|
13210
|
+
function getBrowserCacheDir() {
|
|
13211
|
+
return resolveInstall(process.env, platform(), homedir4()).dir;
|
|
13212
|
+
}
|
|
12945
13213
|
function getBrowserProfileDir(config) {
|
|
12946
13214
|
const configured = (config || getConfig()).TMP_DIR;
|
|
12947
13215
|
let dir;
|
|
12948
13216
|
if (configured && configured.trim().length > 0) {
|
|
12949
|
-
dir =
|
|
13217
|
+
dir = join9(configured, "pi-research", "profiles");
|
|
12950
13218
|
} else {
|
|
12951
|
-
const cacheHome = process.env["XDG_CACHE_HOME"] ||
|
|
12952
|
-
dir =
|
|
13219
|
+
const cacheHome = process.env["XDG_CACHE_HOME"] || join9(homedir4(), ".cache");
|
|
13220
|
+
dir = join9(cacheHome, "pi-research", "profiles");
|
|
12953
13221
|
}
|
|
12954
|
-
if (!
|
|
13222
|
+
if (!existsSync7(dir)) {
|
|
12955
13223
|
try {
|
|
12956
13224
|
mkdirSync3(dir, { recursive: true });
|
|
12957
13225
|
} catch (_e) {
|
|
@@ -12961,14 +13229,10 @@ function getBrowserProfileDir(config) {
|
|
|
12961
13229
|
}
|
|
12962
13230
|
function getBrowserEnv(config) {
|
|
12963
13231
|
const env = { ...process.env };
|
|
12964
|
-
const
|
|
12965
|
-
|
|
12966
|
-
|
|
12967
|
-
|
|
12968
|
-
} else {
|
|
12969
|
-
delete env["PLAYWRIGHT_BROWSERS_PATH"];
|
|
12970
|
-
delete env["CAMOUFOX_INSTALL_DIR"];
|
|
12971
|
-
}
|
|
13232
|
+
const install = resolveInstall(process.env, platform(), homedir4());
|
|
13233
|
+
Object.assign(env, install.overrides);
|
|
13234
|
+
delete env["PLAYWRIGHT_BROWSERS_PATH"];
|
|
13235
|
+
delete env["CAMOUFOX_INSTALL_DIR"];
|
|
12972
13236
|
if (!env["PI_RESEARCH_LOG_FILE"]) {
|
|
12973
13237
|
const logFilePath = getLogger().getLogFilePath();
|
|
12974
13238
|
if (logFilePath) {
|
|
@@ -12988,7 +13252,7 @@ function getBrowserEnv(config) {
|
|
|
12988
13252
|
}
|
|
12989
13253
|
function ensureBrowserCacheDir() {
|
|
12990
13254
|
const cacheDir = getBrowserCacheDir();
|
|
12991
|
-
if (!
|
|
13255
|
+
if (!existsSync7(cacheDir)) {
|
|
12992
13256
|
try {
|
|
12993
13257
|
mkdirSync3(cacheDir, { recursive: true });
|
|
12994
13258
|
} catch (_e) {
|
|
@@ -12997,19 +13261,7 @@ function ensureBrowserCacheDir() {
|
|
|
12997
13261
|
return cacheDir;
|
|
12998
13262
|
}
|
|
12999
13263
|
function getCamoufoxBinaryPath() {
|
|
13000
|
-
|
|
13001
|
-
if (customPath) {
|
|
13002
|
-
return customPath;
|
|
13003
|
-
}
|
|
13004
|
-
const osPlatform = platform();
|
|
13005
|
-
if (osPlatform === "win32") {
|
|
13006
|
-
return getWindowsCamoufoxDir();
|
|
13007
|
-
} else if (osPlatform === "darwin") {
|
|
13008
|
-
return join8(homedir3(), "Library", "Caches", "camoufox");
|
|
13009
|
-
} else {
|
|
13010
|
-
const cacheHome = process.env["XDG_CACHE_HOME"] || join8(homedir3(), ".cache");
|
|
13011
|
-
return join8(cacheHome, "camoufox");
|
|
13012
|
-
}
|
|
13264
|
+
return getBrowserCacheDir();
|
|
13013
13265
|
}
|
|
13014
13266
|
function generateSchedulerVersion(config) {
|
|
13015
13267
|
const c = config || getConfig();
|
|
@@ -13039,16 +13291,15 @@ function probeBrowserNativeDeps(load = defaultNativeDepLoad) {
|
|
|
13039
13291
|
}
|
|
13040
13292
|
}
|
|
13041
13293
|
function defaultNativeDepLoad() {
|
|
13042
|
-
const require2 =
|
|
13043
|
-
|
|
13044
|
-
const db = new Database(":memory:");
|
|
13045
|
-
db.close();
|
|
13294
|
+
const require2 = createRequire5(import.meta.url);
|
|
13295
|
+
require2("impit");
|
|
13046
13296
|
}
|
|
13047
13297
|
function isBrowserAvailable() {
|
|
13048
13298
|
if (isFullMockMode()) return false;
|
|
13049
13299
|
try {
|
|
13050
|
-
import.meta.resolve("camoufox
|
|
13051
|
-
|
|
13300
|
+
import.meta.resolve("@camoufox/camoufox");
|
|
13301
|
+
const root = findPackageRoot(import.meta.url);
|
|
13302
|
+
return isUsable(getCamoufoxBinaryPath(), root ? loadPin(root) : null);
|
|
13052
13303
|
} catch {
|
|
13053
13304
|
return false;
|
|
13054
13305
|
}
|
|
@@ -13220,9 +13471,9 @@ var BrowserClient = class {
|
|
|
13220
13471
|
get targetPort() {
|
|
13221
13472
|
return this.port;
|
|
13222
13473
|
}
|
|
13223
|
-
async request(
|
|
13474
|
+
async request(path21, data, signal, config) {
|
|
13224
13475
|
const start = Date.now();
|
|
13225
|
-
const operation =
|
|
13476
|
+
const operation = path21.includes("/search") ? "search" : path21.includes("/scrape") ? "browser-task" : path21.includes("/healthcheck") ? "healthcheck" : "network";
|
|
13226
13477
|
return new Promise((resolve9, reject) => {
|
|
13227
13478
|
const agent = getClientAgent();
|
|
13228
13479
|
const timeoutMs = resolveClientRequestTimeoutMs(operation, config);
|
|
@@ -13247,7 +13498,7 @@ var BrowserClient = class {
|
|
|
13247
13498
|
resolved = true;
|
|
13248
13499
|
abortCleanup?.();
|
|
13249
13500
|
controller.abort();
|
|
13250
|
-
const error = new Error(`[BrowserClient] Request to ${
|
|
13501
|
+
const error = new Error(`[BrowserClient] Request to ${path21} timed out after ${timeoutMs}ms (Shared queue may be deep)`);
|
|
13251
13502
|
errorTracker.trackError(error, {
|
|
13252
13503
|
component: "browser-manager",
|
|
13253
13504
|
operation,
|
|
@@ -13260,7 +13511,7 @@ var BrowserClient = class {
|
|
|
13260
13511
|
const req = http4.request({
|
|
13261
13512
|
hostname: "127.0.0.1",
|
|
13262
13513
|
port: this.port,
|
|
13263
|
-
path:
|
|
13514
|
+
path: path21,
|
|
13264
13515
|
method: "POST",
|
|
13265
13516
|
agent,
|
|
13266
13517
|
signal: controller.signal,
|
|
@@ -13284,7 +13535,7 @@ var BrowserClient = class {
|
|
|
13284
13535
|
const error = new Error(
|
|
13285
13536
|
`Browser pool leader is draining (${parsed.reason || "leadership-lost"}) \u2014 re-elect and retry`
|
|
13286
13537
|
);
|
|
13287
|
-
logger.debug(`[BrowserClient] Leader draining on ${
|
|
13538
|
+
logger.debug(`[BrowserClient] Leader draining on ${path21}; re-election required.`);
|
|
13288
13539
|
errorTracker.trackError(error, {
|
|
13289
13540
|
component: "browser-manager",
|
|
13290
13541
|
operation,
|
|
@@ -13300,7 +13551,7 @@ var BrowserClient = class {
|
|
|
13300
13551
|
});
|
|
13301
13552
|
reject(error);
|
|
13302
13553
|
} else {
|
|
13303
|
-
logger.debug(`[BrowserClient] Request ${
|
|
13554
|
+
logger.debug(`[BrowserClient] Request ${path21} completed in ${duration}ms`);
|
|
13304
13555
|
resolve9(parsed);
|
|
13305
13556
|
}
|
|
13306
13557
|
} catch (_e) {
|
|
@@ -13320,7 +13571,7 @@ var BrowserClient = class {
|
|
|
13320
13571
|
resolved = true;
|
|
13321
13572
|
clearTimeout(timer);
|
|
13322
13573
|
abortCleanup?.();
|
|
13323
|
-
const error = new Error(`[BrowserClient] Response stream error on ${
|
|
13574
|
+
const error = new Error(`[BrowserClient] Response stream error on ${path21}: ${err.message}`);
|
|
13324
13575
|
errorTracker.trackError(error, {
|
|
13325
13576
|
component: "browser-manager",
|
|
13326
13577
|
operation,
|
|
@@ -13339,23 +13590,23 @@ var BrowserClient = class {
|
|
|
13339
13590
|
let errorMsg;
|
|
13340
13591
|
let errorType;
|
|
13341
13592
|
if (nodeErr.code === "ECONNRESET" || nodeErr.code === "EPIPE") {
|
|
13342
|
-
errorMsg = `Browser pool socket ${
|
|
13593
|
+
errorMsg = `Browser pool socket ${path21} closed (pool likely busy or restarting) - ${err.message}`;
|
|
13343
13594
|
errorType = "connection_reset";
|
|
13344
13595
|
} else if (nodeErr.code === "ECONNREFUSED") {
|
|
13345
|
-
errorMsg = `Browser pool ${
|
|
13596
|
+
errorMsg = `Browser pool ${path21} unreachable (server restarting or torn down) - ${err.message}`;
|
|
13346
13597
|
errorType = "connection_refused";
|
|
13347
13598
|
} else if (nodeErr.code === "ETIMEDOUT") {
|
|
13348
|
-
errorMsg = `Browser pool ${
|
|
13599
|
+
errorMsg = `Browser pool ${path21} timed out (slow browser response) - ${err.message}`;
|
|
13349
13600
|
errorType = "timeout";
|
|
13350
13601
|
} else {
|
|
13351
|
-
errorMsg = `Browser pool ${
|
|
13602
|
+
errorMsg = `Browser pool ${path21} error: ${err.message}`;
|
|
13352
13603
|
errorType = "unknown";
|
|
13353
13604
|
}
|
|
13354
13605
|
const error = new Error(errorMsg);
|
|
13355
13606
|
if (isTransientSocketError(error)) {
|
|
13356
|
-
logger.debug(`[BrowserClient] Transient failure talking to http://127.0.0.1:${this.port}${
|
|
13607
|
+
logger.debug(`[BrowserClient] Transient failure talking to http://127.0.0.1:${this.port}${path21} (caller will retry): ${errorMsg}`);
|
|
13357
13608
|
} else {
|
|
13358
|
-
logger.error(`[BrowserClient] Request to http://127.0.0.1:${this.port}${
|
|
13609
|
+
logger.error(`[BrowserClient] Request to http://127.0.0.1:${this.port}${path21} failed:`, errorMsg);
|
|
13359
13610
|
errorTracker.trackError(error, {
|
|
13360
13611
|
component: "browser-manager",
|
|
13361
13612
|
operation,
|
|
@@ -13607,17 +13858,17 @@ var BrowserServer = class {
|
|
|
13607
13858
|
init_logger();
|
|
13608
13859
|
import { exec } from "node:child_process";
|
|
13609
13860
|
import { promisify } from "node:util";
|
|
13610
|
-
import * as
|
|
13861
|
+
import * as os5 from "node:os";
|
|
13611
13862
|
var execAsync = promisify(exec);
|
|
13612
13863
|
async function cleanupOrphanedCamoufoxProcesses() {
|
|
13613
|
-
const
|
|
13864
|
+
const platform5 = os5.platform();
|
|
13614
13865
|
try {
|
|
13615
|
-
if (
|
|
13866
|
+
if (platform5 === "darwin" || platform5 === "linux") {
|
|
13616
13867
|
await cleanupOrphanedProcessesUnix();
|
|
13617
|
-
} else if (
|
|
13868
|
+
} else if (platform5 === "win32") {
|
|
13618
13869
|
await cleanupOrphanedProcessesWindows();
|
|
13619
13870
|
} else {
|
|
13620
|
-
logger.warn(`[BrowserCleanup] Platform ${
|
|
13871
|
+
logger.warn(`[BrowserCleanup] Platform ${platform5} not supported for orphan cleanup`);
|
|
13621
13872
|
}
|
|
13622
13873
|
} catch (error) {
|
|
13623
13874
|
const msg2 = error instanceof Error ? error.message : String(error);
|
|
@@ -13744,11 +13995,11 @@ async function cleanupOrphanedProcessesWindows() {
|
|
|
13744
13995
|
}
|
|
13745
13996
|
}
|
|
13746
13997
|
async function getBrowserPidsForWorkers(workerPids) {
|
|
13747
|
-
const
|
|
13998
|
+
const platform5 = os5.platform();
|
|
13748
13999
|
const pids = [];
|
|
13749
14000
|
if (!workerPids || workerPids.length === 0) return pids;
|
|
13750
14001
|
try {
|
|
13751
|
-
if (
|
|
14002
|
+
if (platform5 === "darwin" || platform5 === "linux") {
|
|
13752
14003
|
const { stdout } = await execAsync('ps -eo pid,ppid,comm | grep -E "(firefox|camoufox)" | grep -v grep').catch(() => ({ stdout: "" }));
|
|
13753
14004
|
const lines = stdout.trim().split("\n");
|
|
13754
14005
|
for (const line of lines) {
|
|
@@ -13759,7 +14010,7 @@ async function getBrowserPidsForWorkers(workerPids) {
|
|
|
13759
14010
|
if (workerPids.includes(ppid)) pids.push(pid);
|
|
13760
14011
|
}
|
|
13761
14012
|
}
|
|
13762
|
-
} else if (
|
|
14013
|
+
} else if (platform5 === "win32") {
|
|
13763
14014
|
const procs = await queryWindowsProcesses();
|
|
13764
14015
|
for (const proc of procs) {
|
|
13765
14016
|
if (!PI_BROWSER_MARKER.test(proc.commandLine)) continue;
|
|
@@ -13774,10 +14025,10 @@ async function getBrowserPidsForWorkers(workerPids) {
|
|
|
13774
14025
|
}
|
|
13775
14026
|
async function killBrowserProcesses(pids) {
|
|
13776
14027
|
if (!pids || pids.length === 0) return;
|
|
13777
|
-
const
|
|
14028
|
+
const platform5 = os5.platform();
|
|
13778
14029
|
await Promise.all(pids.map(async (pid) => {
|
|
13779
14030
|
try {
|
|
13780
|
-
if (
|
|
14031
|
+
if (platform5 === "win32") {
|
|
13781
14032
|
await execAsync(`taskkill /PID ${pid} /F /T`);
|
|
13782
14033
|
logger.debug(`[BrowserCleanup] Terminated worker browser process: PID ${pid}`);
|
|
13783
14034
|
} else {
|
|
@@ -14508,7 +14759,7 @@ async function getBrowserInitLock(container) {
|
|
|
14508
14759
|
if (lock) return lock;
|
|
14509
14760
|
const pathConfig = await getService(ServiceNames.STATE_PATH_CONFIGURATION, void 0, container);
|
|
14510
14761
|
const processLifecycle = await getService(ServiceNames.PROCESS_LIFECYCLE, void 0, container);
|
|
14511
|
-
const lockFilePath =
|
|
14762
|
+
const lockFilePath = path11.join(pathConfig.getLockDirPath(), "browser-init.lock");
|
|
14512
14763
|
lock = new FileLockService({
|
|
14513
14764
|
lockFilePath,
|
|
14514
14765
|
lockTimeout: 6e4,
|
|
@@ -14809,8 +15060,8 @@ init_config();
|
|
|
14809
15060
|
init_logger();
|
|
14810
15061
|
init_atomic_replace();
|
|
14811
15062
|
init_metrics();
|
|
14812
|
-
import * as
|
|
14813
|
-
import * as
|
|
15063
|
+
import * as fs9 from "node:fs/promises";
|
|
15064
|
+
import * as path12 from "node:path";
|
|
14814
15065
|
import * as crypto7 from "node:crypto";
|
|
14815
15066
|
import process2 from "node:process";
|
|
14816
15067
|
|
|
@@ -15048,12 +15299,12 @@ var StateManager = class {
|
|
|
15048
15299
|
} = options;
|
|
15049
15300
|
let stateDir = providedStateDir;
|
|
15050
15301
|
if (!stateDir) {
|
|
15051
|
-
stateDir =
|
|
15302
|
+
stateDir = path12.join(getGlobalConfigDir(), "state");
|
|
15052
15303
|
}
|
|
15053
|
-
this.stateFilePath =
|
|
15054
|
-
this.lockDirPath =
|
|
15055
|
-
this.backupDirPath =
|
|
15056
|
-
this.lockFilePath =
|
|
15304
|
+
this.stateFilePath = path12.join(stateDir, "research-state.json");
|
|
15305
|
+
this.lockDirPath = path12.join(stateDir, ".locks");
|
|
15306
|
+
this.backupDirPath = path12.join(stateDir, "backups");
|
|
15307
|
+
this.lockFilePath = path12.join(this.lockDirPath, "research-state.lock");
|
|
15057
15308
|
this.fileLockService = fileLockService;
|
|
15058
15309
|
this.processLifecycle = processLifecycle;
|
|
15059
15310
|
this.gpuResourceService = gpuResourceService;
|
|
@@ -15068,13 +15319,13 @@ var StateManager = class {
|
|
|
15068
15319
|
*/
|
|
15069
15320
|
async ensureDirectories() {
|
|
15070
15321
|
const dirs = [
|
|
15071
|
-
|
|
15322
|
+
path12.dirname(this.stateFilePath),
|
|
15072
15323
|
this.lockDirPath,
|
|
15073
15324
|
this.backupDirPath
|
|
15074
15325
|
];
|
|
15075
15326
|
for (const dir of dirs) {
|
|
15076
15327
|
try {
|
|
15077
|
-
await
|
|
15328
|
+
await fs9.mkdir(dir, { recursive: true, mode: 448 });
|
|
15078
15329
|
} catch (err) {
|
|
15079
15330
|
logger.error(`[StateManager] Failed to create directory ${dir}:`, err);
|
|
15080
15331
|
throw err;
|
|
@@ -15101,17 +15352,17 @@ var StateManager = class {
|
|
|
15101
15352
|
if (this.orphanTmpSwept) return;
|
|
15102
15353
|
this.orphanTmpSwept = true;
|
|
15103
15354
|
const STALE_TMP_MS = 5 * 60 * 1e3;
|
|
15104
|
-
const stateDir =
|
|
15355
|
+
const stateDir = path12.dirname(this.stateFilePath);
|
|
15105
15356
|
const cutoff = Date.now() - STALE_TMP_MS;
|
|
15106
15357
|
try {
|
|
15107
|
-
const entries = await
|
|
15358
|
+
const entries = await fs9.readdir(stateDir);
|
|
15108
15359
|
for (const entry of entries) {
|
|
15109
15360
|
if (!entry.startsWith("research-state-") || !entry.endsWith(".tmp")) continue;
|
|
15110
|
-
const filePath =
|
|
15361
|
+
const filePath = path12.join(stateDir, entry);
|
|
15111
15362
|
try {
|
|
15112
|
-
const stats = await
|
|
15363
|
+
const stats = await fs9.stat(filePath);
|
|
15113
15364
|
if (!stats.isFile() || stats.mtimeMs >= cutoff) continue;
|
|
15114
|
-
await
|
|
15365
|
+
await fs9.unlink(filePath);
|
|
15115
15366
|
logger.debug(`[StateManager] Removed orphaned state temp file: ${entry}`);
|
|
15116
15367
|
} catch {
|
|
15117
15368
|
}
|
|
@@ -15156,7 +15407,7 @@ var StateManager = class {
|
|
|
15156
15407
|
return this.getDefaultState();
|
|
15157
15408
|
}
|
|
15158
15409
|
try {
|
|
15159
|
-
const content = await
|
|
15410
|
+
const content = await fs9.readFile(this.stateFilePath, "utf-8");
|
|
15160
15411
|
const state = JSON.parse(content);
|
|
15161
15412
|
return this.validator.migrateAndValidate(state);
|
|
15162
15413
|
} catch (error) {
|
|
@@ -15185,7 +15436,7 @@ var StateManager = class {
|
|
|
15185
15436
|
);
|
|
15186
15437
|
try {
|
|
15187
15438
|
await this.recoverFromCorruptionDirect();
|
|
15188
|
-
const recovered = await
|
|
15439
|
+
const recovered = await fs9.readFile(this.stateFilePath, "utf-8");
|
|
15189
15440
|
return this.validator.migrateAndValidate(JSON.parse(recovered));
|
|
15190
15441
|
} catch (recoveryError) {
|
|
15191
15442
|
logger.error(
|
|
@@ -15244,9 +15495,9 @@ var StateManager = class {
|
|
|
15244
15495
|
logger.warn(`[StateManager] Backup before write failed (continuing): ${err instanceof Error ? err.message : String(err)}`);
|
|
15245
15496
|
});
|
|
15246
15497
|
const tempFileName = `research-state-${crypto7.randomBytes(16).toString("hex")}.tmp`;
|
|
15247
|
-
tempFilePath =
|
|
15498
|
+
tempFilePath = path12.join(path12.dirname(this.stateFilePath), tempFileName);
|
|
15248
15499
|
const content = JSON.stringify(state, null, 2);
|
|
15249
|
-
const fh = await
|
|
15500
|
+
const fh = await fs9.open(tempFilePath, "w", 384);
|
|
15250
15501
|
try {
|
|
15251
15502
|
await fh.writeFile(content, { encoding: "utf-8" });
|
|
15252
15503
|
await fh.sync();
|
|
@@ -15261,7 +15512,7 @@ var StateManager = class {
|
|
|
15261
15512
|
metrics.increment("state_write_non_atomic_total", 1);
|
|
15262
15513
|
const tmp = tempFilePath;
|
|
15263
15514
|
tempFilePath = null;
|
|
15264
|
-
await
|
|
15515
|
+
await fs9.unlink(tmp).catch((err) => {
|
|
15265
15516
|
logger.warn("[StateManager] Failed to remove temp file after copy fallback (write succeeded):", err);
|
|
15266
15517
|
});
|
|
15267
15518
|
}
|
|
@@ -15271,7 +15522,7 @@ var StateManager = class {
|
|
|
15271
15522
|
throw new Error(`Failed to write state: ${error instanceof Error ? error.message : String(error)}`, { cause: error });
|
|
15272
15523
|
} finally {
|
|
15273
15524
|
if (tempFilePath) {
|
|
15274
|
-
await
|
|
15525
|
+
await fs9.unlink(tempFilePath).catch((err) => {
|
|
15275
15526
|
logger.warn("[StateManager] Failed to clean up temp file:", err);
|
|
15276
15527
|
});
|
|
15277
15528
|
}
|
|
@@ -15690,7 +15941,7 @@ var MetricsService = class {
|
|
|
15690
15941
|
// src/infrastructure/process-lifecycle-service.ts
|
|
15691
15942
|
init_service_registry();
|
|
15692
15943
|
init_service_names();
|
|
15693
|
-
import * as
|
|
15944
|
+
import * as fs10 from "node:fs/promises";
|
|
15694
15945
|
import { execFile } from "node:child_process";
|
|
15695
15946
|
function parseEtime(value) {
|
|
15696
15947
|
const m = /^(?:(\d+)-)?(?:(\d+):)?(\d+):(\d+)$/.exec(value.trim());
|
|
@@ -15737,7 +15988,7 @@ var ProcessLifecycleService = class _ProcessLifecycleService {
|
|
|
15737
15988
|
async getProcessStartTime(pid) {
|
|
15738
15989
|
if (process.platform === "linux") {
|
|
15739
15990
|
try {
|
|
15740
|
-
const stat6 = await
|
|
15991
|
+
const stat6 = await fs10.readFile(`/proc/${pid}/stat`, "utf8");
|
|
15741
15992
|
const lastParenIndex = stat6.lastIndexOf(")");
|
|
15742
15993
|
if (lastParenIndex === -1) return null;
|
|
15743
15994
|
const partsAfterName = stat6.substring(lastParenIndex + 2).trim().split(/\s+/);
|
|
@@ -16316,8 +16567,8 @@ var StateValidator = class {
|
|
|
16316
16567
|
if (!Value4.Check(SingletonStateSchema, coerced)) {
|
|
16317
16568
|
const errors = [...Value4.Errors(SingletonStateSchema, coerced)];
|
|
16318
16569
|
const errorMsg = errors.map((e) => {
|
|
16319
|
-
const
|
|
16320
|
-
return `${
|
|
16570
|
+
const path21 = String(e.path || "root");
|
|
16571
|
+
return `${path21}: ${e.message} (${JSON.stringify(e.value)})`;
|
|
16321
16572
|
}).join(", ");
|
|
16322
16573
|
throw new Error(`Invalid state: ${errorMsg}`);
|
|
16323
16574
|
}
|
|
@@ -16364,45 +16615,45 @@ var StateValidator = class {
|
|
|
16364
16615
|
init_logger();
|
|
16365
16616
|
init_metrics();
|
|
16366
16617
|
init_config();
|
|
16367
|
-
import { dirname as
|
|
16368
|
-
import { existsSync as
|
|
16618
|
+
import { dirname as dirname10, join as join14 } from "node:path";
|
|
16619
|
+
import { existsSync as existsSync9 } from "node:fs";
|
|
16369
16620
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
16370
16621
|
import { FixedClusterPool, WorkerChoiceStrategies } from "poolifier";
|
|
16371
16622
|
|
|
16372
16623
|
// src/infrastructure/browser/ensure-browser.ts
|
|
16373
16624
|
init_logger();
|
|
16374
16625
|
import { spawn as spawn2 } from "node:child_process";
|
|
16375
|
-
import { createRequire as
|
|
16626
|
+
import { createRequire as createRequire6 } from "node:module";
|
|
16376
16627
|
import { createHash as createHash2 } from "node:crypto";
|
|
16377
|
-
import { existsSync as
|
|
16378
|
-
import { join as
|
|
16379
|
-
import { tmpdir as tmpdir2 } from "node:os";
|
|
16628
|
+
import { existsSync as existsSync8, fstatSync, openSync as openSync4, closeSync as closeSync4, rmSync as rmSync2, readFileSync as readFileSync5, createWriteStream } from "node:fs";
|
|
16629
|
+
import { join as join12, dirname as dirname9 } from "node:path";
|
|
16630
|
+
import { tmpdir as tmpdir2, platform as platform3, homedir as homedir5 } from "node:os";
|
|
16380
16631
|
var FETCH_TIMEOUT_MS = 45 * 60 * 1e3;
|
|
16381
16632
|
var STALE_LOCK_MS = 55 * 60 * 1e3;
|
|
16382
16633
|
var WAIT_FOR_PEER_MS = 50 * 60 * 1e3;
|
|
16383
16634
|
var POLL_INTERVAL_MS = 2e3;
|
|
16384
16635
|
var inFlight = null;
|
|
16636
|
+
function currentPin() {
|
|
16637
|
+
const root = findPackageRoot(import.meta.url);
|
|
16638
|
+
return root ? loadPin(root) : null;
|
|
16639
|
+
}
|
|
16385
16640
|
function isBrowserBinaryPresent() {
|
|
16386
16641
|
try {
|
|
16387
|
-
|
|
16388
|
-
if (!existsSync7(cacheDir)) return false;
|
|
16389
|
-
return readdirSync3(cacheDir).some((entry) => {
|
|
16390
|
-
try {
|
|
16391
|
-
return statSync3(join11(cacheDir, entry)).isDirectory();
|
|
16392
|
-
} catch {
|
|
16393
|
-
return false;
|
|
16394
|
-
}
|
|
16395
|
-
});
|
|
16642
|
+
return isUsable(getCamoufoxBinaryPath(), currentPin());
|
|
16396
16643
|
} catch {
|
|
16397
16644
|
return false;
|
|
16398
16645
|
}
|
|
16399
16646
|
}
|
|
16400
|
-
function
|
|
16647
|
+
function resolveCamoufoxCli() {
|
|
16401
16648
|
try {
|
|
16402
|
-
const require2 =
|
|
16403
|
-
const pkgJson = require2.resolve("camoufox
|
|
16404
|
-
const
|
|
16405
|
-
|
|
16649
|
+
const require2 = createRequire6(import.meta.url);
|
|
16650
|
+
const pkgJson = require2.resolve("@camoufox/camoufox/package.json");
|
|
16651
|
+
const pkg = JSON.parse(readFileSync5(pkgJson, "utf8"));
|
|
16652
|
+
const rel = typeof pkg.bin === "string" ? pkg.bin : pkg.bin?.["camoufox"];
|
|
16653
|
+
if (rel) {
|
|
16654
|
+
const entry = join12(dirname9(pkgJson), rel);
|
|
16655
|
+
if (existsSync8(entry)) return entry;
|
|
16656
|
+
}
|
|
16406
16657
|
} catch {
|
|
16407
16658
|
}
|
|
16408
16659
|
return null;
|
|
@@ -16410,23 +16661,46 @@ function resolveCamoufoxBin() {
|
|
|
16410
16661
|
var delay = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
16411
16662
|
function runFetch() {
|
|
16412
16663
|
return new Promise((resolve9, reject) => {
|
|
16413
|
-
const
|
|
16414
|
-
|
|
16415
|
-
|
|
16416
|
-
|
|
16417
|
-
|
|
16664
|
+
const cli = resolveCamoufoxCli();
|
|
16665
|
+
if (!cli) {
|
|
16666
|
+
reject(new Error("@camoufox/camoufox is not installed (cannot find its CLI); run npm install"));
|
|
16667
|
+
return;
|
|
16668
|
+
}
|
|
16669
|
+
const install = resolveInstall(process.env, platform3(), homedir5());
|
|
16670
|
+
const env = { ...process.env, ...install.overrides };
|
|
16671
|
+
delete env["CAMOUFOX_INSTALL_DIR"];
|
|
16672
|
+
delete env["PLAYWRIGHT_BROWSERS_PATH"];
|
|
16673
|
+
logger.info(`[ensure-browser] Camoufox not found; fetching the browser (this runs once, ~1.3GB): ${process.execPath} ${cli} fetch`);
|
|
16674
|
+
const outLogPath = join12(tmpdir2(), `pi-research-camoufox-fetch-${process.pid}-${Date.now()}.log`);
|
|
16675
|
+
const outStream = createWriteStream(outLogPath, { mode: 384 });
|
|
16676
|
+
const child = spawn2(process.execPath, [cli, "fetch"], { stdio: ["ignore", "pipe", "inherit"], env });
|
|
16677
|
+
if (child.stdout) {
|
|
16678
|
+
child.stdout.pipe(outStream);
|
|
16679
|
+
}
|
|
16418
16680
|
const timer = setTimeout(() => {
|
|
16419
16681
|
child.kill("SIGKILL");
|
|
16420
16682
|
reject(new Error(`camoufox fetch timed out after ${FETCH_TIMEOUT_MS}ms`));
|
|
16421
16683
|
}, FETCH_TIMEOUT_MS);
|
|
16422
|
-
|
|
16684
|
+
const finish = (err, ok) => {
|
|
16423
16685
|
clearTimeout(timer);
|
|
16424
|
-
|
|
16686
|
+
void outStream.close();
|
|
16687
|
+
if (err) reject(err);
|
|
16688
|
+
else resolve9(ok);
|
|
16689
|
+
};
|
|
16690
|
+
child.on("error", (err) => {
|
|
16691
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
16692
|
+
finish(new Error(`${message} (fetch output logged at ${outLogPath})`), false);
|
|
16425
16693
|
});
|
|
16426
16694
|
child.on("exit", (code) => {
|
|
16427
|
-
|
|
16428
|
-
|
|
16429
|
-
|
|
16695
|
+
if (code === 0) {
|
|
16696
|
+
try {
|
|
16697
|
+
rmSync2(outLogPath, { force: true });
|
|
16698
|
+
} catch {
|
|
16699
|
+
}
|
|
16700
|
+
finish(null, true);
|
|
16701
|
+
} else {
|
|
16702
|
+
finish(new Error(`camoufox fetch exited with code ${code} (fetch output logged at ${outLogPath})`), false);
|
|
16703
|
+
}
|
|
16430
16704
|
});
|
|
16431
16705
|
});
|
|
16432
16706
|
}
|
|
@@ -16445,7 +16719,7 @@ async function provision() {
|
|
|
16445
16719
|
return;
|
|
16446
16720
|
}
|
|
16447
16721
|
const cacheKey = createHash2("sha256").update(getCamoufoxBinaryPath()).digest("hex").slice(0, 16);
|
|
16448
|
-
const lockPath =
|
|
16722
|
+
const lockPath = join12(tmpdir2(), `pi-research-camoufox-fetch-${cacheKey}.lock`);
|
|
16449
16723
|
let haveLock = false;
|
|
16450
16724
|
try {
|
|
16451
16725
|
closeSync4(openSync4(lockPath, "wx", 384));
|
|
@@ -16460,7 +16734,7 @@ async function provision() {
|
|
|
16460
16734
|
closeSync4(fd);
|
|
16461
16735
|
}
|
|
16462
16736
|
if (Date.now() - mtimeMs > STALE_LOCK_MS) {
|
|
16463
|
-
|
|
16737
|
+
rmSync2(lockPath, { force: true });
|
|
16464
16738
|
closeSync4(openSync4(lockPath, "wx", 384));
|
|
16465
16739
|
haveLock = true;
|
|
16466
16740
|
}
|
|
@@ -16473,16 +16747,15 @@ async function provision() {
|
|
|
16473
16747
|
}
|
|
16474
16748
|
try {
|
|
16475
16749
|
if (isBrowserBinaryPresent()) return;
|
|
16476
|
-
await runFetch();
|
|
16477
|
-
if (
|
|
16478
|
-
|
|
16479
|
-
} else {
|
|
16480
|
-
logger.warn("[ensure-browser] camoufox fetch reported success but no binary was found.");
|
|
16750
|
+
const result = await provisionBrowser(getCamoufoxBinaryPath(), runFetch, (m) => logger.info(`[ensure-browser] ${m}`), currentPin());
|
|
16751
|
+
if (result.status === "failed") {
|
|
16752
|
+
throw new Error(result.reason ?? "camoufox fetch failed");
|
|
16481
16753
|
}
|
|
16754
|
+
logger.info("[ensure-browser] Camoufox browser is ready.");
|
|
16482
16755
|
} finally {
|
|
16483
16756
|
if (haveLock) {
|
|
16484
16757
|
try {
|
|
16485
|
-
|
|
16758
|
+
rmSync2(lockPath, { force: true });
|
|
16486
16759
|
} catch {
|
|
16487
16760
|
}
|
|
16488
16761
|
}
|
|
@@ -16500,16 +16773,16 @@ function ensureBrowserInstalled() {
|
|
|
16500
16773
|
|
|
16501
16774
|
// src/infrastructure/browser/cleanup-utils.ts
|
|
16502
16775
|
init_logger();
|
|
16503
|
-
import * as
|
|
16504
|
-
import * as
|
|
16505
|
-
import * as
|
|
16776
|
+
import * as fs11 from "node:fs/promises";
|
|
16777
|
+
import * as path13 from "node:path";
|
|
16778
|
+
import * as os6 from "node:os";
|
|
16506
16779
|
var DEFAULT_STALE_MS = 30 * 24 * 60 * 60 * 1e3;
|
|
16507
16780
|
var LOCK_FILE_NAMES = ["lock", ".parentlock", "parent.lock"];
|
|
16508
16781
|
var LOCK_FRESHNESS_MS = 2 * 60 * 1e3;
|
|
16509
16782
|
async function isWindowsParentLockHeld(lockPath) {
|
|
16510
16783
|
let handle;
|
|
16511
16784
|
try {
|
|
16512
|
-
handle = await
|
|
16785
|
+
handle = await fs11.open(lockPath, "r");
|
|
16513
16786
|
} catch (e) {
|
|
16514
16787
|
const code = e?.code;
|
|
16515
16788
|
if (code === "ENOENT") return false;
|
|
@@ -16536,16 +16809,16 @@ function isPidAlive(pid) {
|
|
|
16536
16809
|
}
|
|
16537
16810
|
async function isProfileLive(profileDir) {
|
|
16538
16811
|
for (const name of LOCK_FILE_NAMES) {
|
|
16539
|
-
const lockPath =
|
|
16812
|
+
const lockPath = path13.join(profileDir, name);
|
|
16540
16813
|
let lst;
|
|
16541
16814
|
try {
|
|
16542
|
-
lst = await
|
|
16815
|
+
lst = await fs11.lstat(lockPath);
|
|
16543
16816
|
} catch {
|
|
16544
16817
|
continue;
|
|
16545
16818
|
}
|
|
16546
16819
|
if (lst.isSymbolicLink()) {
|
|
16547
16820
|
try {
|
|
16548
|
-
const pid = parsePidFromLockTarget(await
|
|
16821
|
+
const pid = parsePidFromLockTarget(await fs11.readlink(lockPath));
|
|
16549
16822
|
if (pid !== null) return isPidAlive(pid);
|
|
16550
16823
|
} catch {
|
|
16551
16824
|
}
|
|
@@ -16562,42 +16835,42 @@ async function isProfileLive(profileDir) {
|
|
|
16562
16835
|
async function canonicalizeForCompare(p) {
|
|
16563
16836
|
let resolved;
|
|
16564
16837
|
try {
|
|
16565
|
-
resolved = await
|
|
16838
|
+
resolved = await fs11.realpath(p);
|
|
16566
16839
|
} catch {
|
|
16567
|
-
resolved =
|
|
16840
|
+
resolved = path13.resolve(p);
|
|
16568
16841
|
}
|
|
16569
16842
|
return process.platform === "win32" ? resolved.toLowerCase() : resolved;
|
|
16570
16843
|
}
|
|
16571
16844
|
async function isPiOwnedSweepDir(dir) {
|
|
16572
16845
|
const [realDir, realTmp] = await Promise.all([
|
|
16573
16846
|
canonicalizeForCompare(dir),
|
|
16574
|
-
canonicalizeForCompare(
|
|
16847
|
+
canonicalizeForCompare(os6.tmpdir())
|
|
16575
16848
|
]);
|
|
16576
16849
|
if (realDir === realTmp) return false;
|
|
16577
16850
|
const hasPiSegment = (p) => p.split(/[\\/]+/).some((segment) => segment.toLowerCase().startsWith("pi-research"));
|
|
16578
16851
|
return hasPiSegment(realDir);
|
|
16579
16852
|
}
|
|
16580
16853
|
async function cleanupStaleProfiles(baseDir, staleMs = DEFAULT_STALE_MS) {
|
|
16581
|
-
const scanDir = baseDir ??
|
|
16854
|
+
const scanDir = baseDir ?? os6.tmpdir();
|
|
16582
16855
|
const usePrefix = baseDir === void 0;
|
|
16583
16856
|
const prefix = "playwright_firefoxdev_profile-";
|
|
16584
16857
|
let removed = 0;
|
|
16585
16858
|
let errors = 0;
|
|
16586
16859
|
const sweepUnrecognized = usePrefix ? false : await isPiOwnedSweepDir(scanDir);
|
|
16587
16860
|
try {
|
|
16588
|
-
const entries = await
|
|
16861
|
+
const entries = await fs11.readdir(scanDir);
|
|
16589
16862
|
const now = Date.now();
|
|
16590
16863
|
for (const entry of entries) {
|
|
16591
16864
|
if (usePrefix && !entry.startsWith(prefix)) continue;
|
|
16592
|
-
const fullPath =
|
|
16865
|
+
const fullPath = path13.join(scanDir, entry);
|
|
16593
16866
|
try {
|
|
16594
|
-
const stats = await
|
|
16867
|
+
const stats = await fs11.stat(fullPath);
|
|
16595
16868
|
if (!stats.isDirectory()) continue;
|
|
16596
16869
|
const isProfileLike = entry.startsWith(prefix) || entry.startsWith("playwright-artifacts-");
|
|
16597
16870
|
if (isProfileLike) {
|
|
16598
|
-
const inner = await
|
|
16871
|
+
const inner = await fs11.readdir(fullPath);
|
|
16599
16872
|
if (inner.length === 0) {
|
|
16600
|
-
await
|
|
16873
|
+
await fs11.rmdir(fullPath);
|
|
16601
16874
|
removed++;
|
|
16602
16875
|
continue;
|
|
16603
16876
|
}
|
|
@@ -16605,12 +16878,12 @@ async function cleanupStaleProfiles(baseDir, staleMs = DEFAULT_STALE_MS) {
|
|
|
16605
16878
|
logger.debug(`[Cleanup] Skipping profile ${entry} \u2014 owned by a live browser`);
|
|
16606
16879
|
continue;
|
|
16607
16880
|
}
|
|
16608
|
-
await
|
|
16881
|
+
await fs11.rm(fullPath, { recursive: true, force: true, maxRetries: 5, retryDelay: 50 });
|
|
16609
16882
|
removed++;
|
|
16610
16883
|
continue;
|
|
16611
16884
|
}
|
|
16612
16885
|
if (sweepUnrecognized && now - stats.mtimeMs > staleMs && !await isProfileLive(fullPath)) {
|
|
16613
|
-
await
|
|
16886
|
+
await fs11.rm(fullPath, { recursive: true, force: true, maxRetries: 5, retryDelay: 50 });
|
|
16614
16887
|
removed++;
|
|
16615
16888
|
}
|
|
16616
16889
|
} catch (_e) {
|
|
@@ -16630,7 +16903,7 @@ async function cleanupStaleProfiles(baseDir, staleMs = DEFAULT_STALE_MS) {
|
|
|
16630
16903
|
init_log_utils();
|
|
16631
16904
|
import process3 from "node:process";
|
|
16632
16905
|
import cluster from "node:cluster";
|
|
16633
|
-
import * as
|
|
16906
|
+
import * as fs12 from "node:fs/promises";
|
|
16634
16907
|
var workerId = "";
|
|
16635
16908
|
var BENIGN_IPC_CODES = /* @__PURE__ */ new Set(["EPIPE", "ERR_IPC_CHANNEL_CLOSED", "ECONNRESET"]);
|
|
16636
16909
|
function isBenignClusterIpcError(err) {
|
|
@@ -16691,7 +16964,7 @@ function logToDebugFile(level, ...args) {
|
|
|
16691
16964
|
return String(arg);
|
|
16692
16965
|
}).join(" "))
|
|
16693
16966
|
};
|
|
16694
|
-
|
|
16967
|
+
fs12.appendFile(logFile, `${JSON.stringify(entry)}
|
|
16695
16968
|
`).catch(() => {
|
|
16696
16969
|
});
|
|
16697
16970
|
} catch {
|
|
@@ -16703,7 +16976,7 @@ init_service_registry();
|
|
|
16703
16976
|
init_safe_unref();
|
|
16704
16977
|
init_service_names();
|
|
16705
16978
|
var __filename2 = fileURLToPath3(import.meta.url);
|
|
16706
|
-
var __dirname =
|
|
16979
|
+
var __dirname = dirname10(__filename2);
|
|
16707
16980
|
function buildWorkerPoolOptions(config) {
|
|
16708
16981
|
return {
|
|
16709
16982
|
workerChoiceStrategy: WorkerChoiceStrategies.LEAST_USED,
|
|
@@ -16795,8 +17068,8 @@ var WorkerPoolManager = class {
|
|
|
16795
17068
|
void cleanupStaleProfiles(getBrowserProfileDir(config), STALE_PROFILE_MS).catch((e) => logger.debug("[WorkerPoolManager] Profile sweep (profile dir) failed:", e));
|
|
16796
17069
|
void cleanupStaleProfiles(void 0, STALE_PROFILE_MS).catch((e) => logger.debug("[WorkerPoolManager] Profile sweep (legacy tmp) failed:", e));
|
|
16797
17070
|
void cleanupOrphanedCamoufoxProcesses().catch((e) => logger.debug("[WorkerPoolManager] Startup orphan sweep failed:", e));
|
|
16798
|
-
const workerPath =
|
|
16799
|
-
if (!
|
|
17071
|
+
const workerPath = join14(__dirname, "./thread-worker.mjs");
|
|
17072
|
+
if (!existsSync9(workerPath)) {
|
|
16800
17073
|
throw new Error(
|
|
16801
17074
|
`Browser worker bundle is missing at ${workerPath}. It is produced by the build step; reinstall the extension (e.g. \`pi update --extensions\`) or run \`npm install\` in the extension directory so the prepare/build script regenerates it.`
|
|
16802
17075
|
);
|
|
@@ -17044,7 +17317,7 @@ var WorkerPoolManager = class {
|
|
|
17044
17317
|
init_config();
|
|
17045
17318
|
init_service_registry();
|
|
17046
17319
|
init_service_names();
|
|
17047
|
-
import * as
|
|
17320
|
+
import * as path14 from "node:path";
|
|
17048
17321
|
var StatePathConfiguration = class {
|
|
17049
17322
|
name = ServiceNames.STATE_PATH_CONFIGURATION;
|
|
17050
17323
|
lifecycle = "uninitialized" /* UNINITIALIZED */;
|
|
@@ -17056,13 +17329,13 @@ var StatePathConfiguration = class {
|
|
|
17056
17329
|
lockFilePath;
|
|
17057
17330
|
projectSettingsPath;
|
|
17058
17331
|
constructor(stateDir) {
|
|
17059
|
-
const resolvedStateDir = stateDir &&
|
|
17332
|
+
const resolvedStateDir = stateDir && path14.isAbsolute(stateDir) ? stateDir : getStateDir();
|
|
17060
17333
|
this.stateDir = resolvedStateDir;
|
|
17061
|
-
this.stateFilePath =
|
|
17062
|
-
this.lockDirPath =
|
|
17063
|
-
this.backupDirPath =
|
|
17064
|
-
this.lockFilePath =
|
|
17065
|
-
this.projectSettingsPath =
|
|
17334
|
+
this.stateFilePath = path14.join(resolvedStateDir, "research-state.json");
|
|
17335
|
+
this.lockDirPath = path14.join(resolvedStateDir, ".locks");
|
|
17336
|
+
this.backupDirPath = path14.join(resolvedStateDir, "backups");
|
|
17337
|
+
this.lockFilePath = path14.join(this.lockDirPath, "research-state.lock");
|
|
17338
|
+
this.projectSettingsPath = path14.join(resolvedStateDir, "project-settings.json");
|
|
17066
17339
|
}
|
|
17067
17340
|
async initialize() {
|
|
17068
17341
|
if (this._initialized) {
|
|
@@ -17181,7 +17454,7 @@ var ResearchRunSemaphore = class {
|
|
|
17181
17454
|
if (this._initialized) return;
|
|
17182
17455
|
this.lifecycle = "initializing" /* INITIALIZING */;
|
|
17183
17456
|
try {
|
|
17184
|
-
await import("node:fs/promises").then((
|
|
17457
|
+
await import("node:fs/promises").then((fs19) => fs19.mkdir(this.slotDir, { recursive: true, mode: 448 }));
|
|
17185
17458
|
} catch (err) {
|
|
17186
17459
|
logger.warn(`[ResearchRunSemaphore] Could not create slot dir ${this.slotDir}: ${err instanceof Error ? err.message : String(err)}`);
|
|
17187
17460
|
}
|
|
@@ -17334,9 +17607,9 @@ init_logger();
|
|
|
17334
17607
|
init_atomic_replace();
|
|
17335
17608
|
init_service_registry();
|
|
17336
17609
|
init_service_names();
|
|
17337
|
-
import * as
|
|
17610
|
+
import * as fs13 from "node:fs/promises";
|
|
17338
17611
|
import * as crypto8 from "node:crypto";
|
|
17339
|
-
import * as
|
|
17612
|
+
import * as path15 from "node:path";
|
|
17340
17613
|
var QUARANTINE_KEEP = 5;
|
|
17341
17614
|
var StateBackupManager = class {
|
|
17342
17615
|
name = ServiceNames.STATE_BACKUP_MANAGER;
|
|
@@ -17358,7 +17631,7 @@ var StateBackupManager = class {
|
|
|
17358
17631
|
return;
|
|
17359
17632
|
}
|
|
17360
17633
|
this.lifecycle = "initializing" /* INITIALIZING */;
|
|
17361
|
-
await
|
|
17634
|
+
await fs13.mkdir(this.backupDirPath, { recursive: true, mode: 448 });
|
|
17362
17635
|
this._initialized = true;
|
|
17363
17636
|
this.lifecycle = "initialized" /* INITIALIZED */;
|
|
17364
17637
|
}
|
|
@@ -17372,15 +17645,15 @@ var StateBackupManager = class {
|
|
|
17372
17645
|
async createBackup() {
|
|
17373
17646
|
try {
|
|
17374
17647
|
try {
|
|
17375
|
-
await
|
|
17648
|
+
await fs13.access(this.stateFilePath);
|
|
17376
17649
|
} catch {
|
|
17377
17650
|
return;
|
|
17378
17651
|
}
|
|
17379
|
-
await
|
|
17652
|
+
await fs13.mkdir(this.backupDirPath, { recursive: true, mode: 448 });
|
|
17380
17653
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
17381
17654
|
const backupFileName = `research-state-${timestamp}-${crypto8.randomBytes(4).toString("hex")}.json`;
|
|
17382
|
-
const backupFilePath =
|
|
17383
|
-
await
|
|
17655
|
+
const backupFilePath = path15.join(this.backupDirPath, backupFileName);
|
|
17656
|
+
await fs13.copyFile(this.stateFilePath, backupFilePath);
|
|
17384
17657
|
} catch (error) {
|
|
17385
17658
|
throw new Error(`Failed to create backup: ${error instanceof Error ? error.message : String(error)}`, { cause: error });
|
|
17386
17659
|
}
|
|
@@ -17390,14 +17663,14 @@ var StateBackupManager = class {
|
|
|
17390
17663
|
*/
|
|
17391
17664
|
async cleanupOldBackups() {
|
|
17392
17665
|
try {
|
|
17393
|
-
const entries = await
|
|
17666
|
+
const entries = await fs13.readdir(this.backupDirPath);
|
|
17394
17667
|
if (entries.length <= this.maxBackups) {
|
|
17395
17668
|
return;
|
|
17396
17669
|
}
|
|
17397
17670
|
const backupFiles = [];
|
|
17398
17671
|
for (const entry of entries) {
|
|
17399
|
-
const filePath =
|
|
17400
|
-
const stats = await
|
|
17672
|
+
const filePath = path15.join(this.backupDirPath, entry);
|
|
17673
|
+
const stats = await fs13.stat(filePath);
|
|
17401
17674
|
if (stats.isFile() && entry.startsWith("research-state-") && entry.endsWith(".json")) {
|
|
17402
17675
|
backupFiles.push({ name: entry, mtime: stats.mtime });
|
|
17403
17676
|
}
|
|
@@ -17405,8 +17678,8 @@ var StateBackupManager = class {
|
|
|
17405
17678
|
backupFiles.sort((a, b) => b.mtime.getTime() - a.mtime.getTime());
|
|
17406
17679
|
const backupsToRemove = backupFiles.slice(this.maxBackups);
|
|
17407
17680
|
for (const backupFile of backupsToRemove) {
|
|
17408
|
-
const filePath =
|
|
17409
|
-
await
|
|
17681
|
+
const filePath = path15.join(this.backupDirPath, backupFile.name);
|
|
17682
|
+
await fs13.unlink(filePath);
|
|
17410
17683
|
}
|
|
17411
17684
|
} catch (error) {
|
|
17412
17685
|
logger.error(`Failed to cleanup old backups: ${error instanceof Error ? error.message : String(error)}`);
|
|
@@ -17419,10 +17692,10 @@ var StateBackupManager = class {
|
|
|
17419
17692
|
try {
|
|
17420
17693
|
let backups = [];
|
|
17421
17694
|
try {
|
|
17422
|
-
const entries = await
|
|
17695
|
+
const entries = await fs13.readdir(this.backupDirPath);
|
|
17423
17696
|
for (const entry of entries) {
|
|
17424
|
-
const filePath =
|
|
17425
|
-
const stats = await
|
|
17697
|
+
const filePath = path15.join(this.backupDirPath, entry);
|
|
17698
|
+
const stats = await fs13.stat(filePath);
|
|
17426
17699
|
if (stats.isFile() && entry.startsWith("research-state-") && entry.endsWith(".json")) {
|
|
17427
17700
|
backups.push({ name: entry, mtime: stats.mtime });
|
|
17428
17701
|
}
|
|
@@ -17431,10 +17704,10 @@ var StateBackupManager = class {
|
|
|
17431
17704
|
}
|
|
17432
17705
|
backups.sort((a, b) => b.mtime.getTime() - a.mtime.getTime());
|
|
17433
17706
|
for (const backup of backups) {
|
|
17434
|
-
const backupPath =
|
|
17707
|
+
const backupPath = path15.join(this.backupDirPath, backup.name);
|
|
17435
17708
|
let restoreContent;
|
|
17436
17709
|
try {
|
|
17437
|
-
const content = await
|
|
17710
|
+
const content = await fs13.readFile(backupPath, "utf-8");
|
|
17438
17711
|
const validated = this.validator.migrateAndValidate(JSON.parse(content));
|
|
17439
17712
|
restoreContent = JSON.stringify(validated, null, 2);
|
|
17440
17713
|
} catch (err) {
|
|
@@ -17462,27 +17735,27 @@ var StateBackupManager = class {
|
|
|
17462
17735
|
*/
|
|
17463
17736
|
async quarantineFutureState(fileVersion) {
|
|
17464
17737
|
try {
|
|
17465
|
-
await
|
|
17738
|
+
await fs13.access(this.stateFilePath);
|
|
17466
17739
|
} catch {
|
|
17467
17740
|
return;
|
|
17468
17741
|
}
|
|
17469
|
-
await
|
|
17742
|
+
await fs13.mkdir(this.backupDirPath, { recursive: true, mode: 448 });
|
|
17470
17743
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
17471
17744
|
const name = `research-state-future-v${fileVersion}-${timestamp}.quarantine`;
|
|
17472
|
-
const dest =
|
|
17473
|
-
await
|
|
17745
|
+
const dest = path15.join(this.backupDirPath, name);
|
|
17746
|
+
await fs13.copyFile(this.stateFilePath, dest);
|
|
17474
17747
|
logger.warn(`[StateManager] Quarantined newer-build state (v${fileVersion}) copy at ${dest}`);
|
|
17475
17748
|
await this.pruneQuarantineFiles(QUARANTINE_KEEP);
|
|
17476
17749
|
}
|
|
17477
17750
|
/** Keep only the newest `keep` *.quarantine files; delete the rest. Best-effort. */
|
|
17478
17751
|
async pruneQuarantineFiles(keep) {
|
|
17479
17752
|
try {
|
|
17480
|
-
const entries = await
|
|
17753
|
+
const entries = await fs13.readdir(this.backupDirPath);
|
|
17481
17754
|
const quarantines = [];
|
|
17482
17755
|
for (const entry of entries) {
|
|
17483
17756
|
if (!entry.endsWith(".quarantine")) continue;
|
|
17484
17757
|
try {
|
|
17485
|
-
const stats = await
|
|
17758
|
+
const stats = await fs13.stat(path15.join(this.backupDirPath, entry));
|
|
17486
17759
|
if (stats.isFile()) quarantines.push({ name: entry, mtime: stats.mtimeMs });
|
|
17487
17760
|
} catch {
|
|
17488
17761
|
}
|
|
@@ -17491,7 +17764,7 @@ var StateBackupManager = class {
|
|
|
17491
17764
|
quarantines.sort((a, b) => b.mtime - a.mtime);
|
|
17492
17765
|
for (const stale of quarantines.slice(keep)) {
|
|
17493
17766
|
try {
|
|
17494
|
-
await
|
|
17767
|
+
await fs13.unlink(path15.join(this.backupDirPath, stale.name));
|
|
17495
17768
|
} catch {
|
|
17496
17769
|
}
|
|
17497
17770
|
}
|
|
@@ -17503,11 +17776,11 @@ var StateBackupManager = class {
|
|
|
17503
17776
|
* a Windows copy+delete fallback since rename fails on NTFS when the target exists).
|
|
17504
17777
|
*/
|
|
17505
17778
|
async atomicWriteStateFile(content) {
|
|
17506
|
-
const stateDir =
|
|
17507
|
-
await
|
|
17779
|
+
const stateDir = path15.dirname(this.stateFilePath);
|
|
17780
|
+
await fs13.mkdir(stateDir, { recursive: true, mode: 448 });
|
|
17508
17781
|
const tempFile = `research-state-${crypto8.randomBytes(16).toString("hex")}.tmp`;
|
|
17509
|
-
const tempPath =
|
|
17510
|
-
const fh = await
|
|
17782
|
+
const tempPath = path15.join(stateDir, tempFile);
|
|
17783
|
+
const fh = await fs13.open(tempPath, "w", 384);
|
|
17511
17784
|
try {
|
|
17512
17785
|
await fh.writeFile(content, { encoding: "utf-8" });
|
|
17513
17786
|
await fh.sync();
|
|
@@ -17516,11 +17789,11 @@ var StateBackupManager = class {
|
|
|
17516
17789
|
}
|
|
17517
17790
|
try {
|
|
17518
17791
|
if (await replaceFile(tempPath, this.stateFilePath) === "copied") {
|
|
17519
|
-
await
|
|
17792
|
+
await fs13.unlink(tempPath).catch(() => {
|
|
17520
17793
|
});
|
|
17521
17794
|
}
|
|
17522
17795
|
} catch (renameErr) {
|
|
17523
|
-
await
|
|
17796
|
+
await fs13.unlink(tempPath).catch(() => {
|
|
17524
17797
|
});
|
|
17525
17798
|
throw renameErr;
|
|
17526
17799
|
}
|
|
@@ -18158,7 +18431,7 @@ async function performSearch(queries, config, signal, onProgress, container = ge
|
|
|
18158
18431
|
if (isNativeBindingError(sampleWorkerError)) {
|
|
18159
18432
|
metrics.increment("browser_search_total_failures_total", 1, { cause: "native_binding" });
|
|
18160
18433
|
throw new Error(
|
|
18161
|
-
`Search completely failed: ${reason}, all with a missing native module. This is an incomplete install, not a network problem:
|
|
18434
|
+
`Search completely failed: ${reason}, all with a missing native module. This is an incomplete install, not a network problem: the browser launcher's native dependency (impit) ships a prebuilt binding per platform as a separate package, and npm sometimes drops it (npm/cli#4828). Reinstall this package (npm install, or pi install again) to restore it.` + (sampleWorkerError ? ` Last worker error: ${sampleWorkerError}` : "")
|
|
18162
18435
|
);
|
|
18163
18436
|
}
|
|
18164
18437
|
throw new Error(
|
|
@@ -18339,7 +18612,7 @@ async function checkBrowserCapability() {
|
|
|
18339
18612
|
}
|
|
18340
18613
|
return { healthy: true, diagnostic: { status: mockMode ? "mocked" : "available" } };
|
|
18341
18614
|
} else {
|
|
18342
|
-
return { healthy: false, error: 'Camoufox (browser) not found. Run "npx camoufox
|
|
18615
|
+
return { healthy: false, error: 'Camoufox (browser) not found. Run "npx camoufox fetch" to install the browser.' };
|
|
18343
18616
|
}
|
|
18344
18617
|
}
|
|
18345
18618
|
function knowledgeStoreDisabledStatus() {
|
|
@@ -18931,9 +19204,9 @@ function formatErrorWithCause(err) {
|
|
|
18931
19204
|
function isPrivateIp(ip) {
|
|
18932
19205
|
const parts = ip.split(".").map(Number);
|
|
18933
19206
|
if (parts.length !== 4 || parts.some((p) => isNaN(p))) return false;
|
|
18934
|
-
const [a, b] = parts;
|
|
19207
|
+
const [a, b, c] = parts;
|
|
18935
19208
|
if (a === void 0 || b === void 0) return false;
|
|
18936
|
-
return a === 0 || a === 10 || a === 127 || a === 100 && b >= 64 && b <= 127 || a === 169 && b === 254 || a === 172 && b >= 16 && b <= 31 || a === 192 && b === 168 || a >= 224;
|
|
19209
|
+
return a === 0 || a === 10 || a === 127 || a === 100 && b >= 64 && b <= 127 || a === 169 && b === 254 || a === 172 && b >= 16 && b <= 31 || a === 192 && b === 168 || a === 198 && (b === 18 || b === 19) || a === 192 && b === 0 && (c === 0 || c === 2) || a === 198 && b === 51 && c === 100 || a === 203 && b === 0 && c === 113 || a >= 224;
|
|
18937
19210
|
}
|
|
18938
19211
|
function isPrivateIpv6(ip) {
|
|
18939
19212
|
const normalized = ip.toLowerCase().replace(/^\[|\]$/g, "");
|
|
@@ -19107,11 +19380,11 @@ init_service_registry();
|
|
|
19107
19380
|
init_logger();
|
|
19108
19381
|
init_metrics();
|
|
19109
19382
|
init_error_tracker();
|
|
19110
|
-
import { existsSync as
|
|
19111
|
-
import { dirname as
|
|
19383
|
+
import { existsSync as existsSync12 } from "node:fs";
|
|
19384
|
+
import { dirname as dirname13, join as join21 } from "node:path";
|
|
19112
19385
|
import { fileURLToPath as fileURLToPath4 } from "node:url";
|
|
19113
19386
|
import { Worker } from "node:worker_threads";
|
|
19114
|
-
var MODULE_DIR =
|
|
19387
|
+
var MODULE_DIR = dirname13(fileURLToPath4(import.meta.url));
|
|
19115
19388
|
var PdfExtractError = class extends Error {
|
|
19116
19389
|
kind;
|
|
19117
19390
|
constructor(kind, message, options) {
|
|
@@ -19179,8 +19452,8 @@ var nextRequestId = 1;
|
|
|
19179
19452
|
var pendingRequests = /* @__PURE__ */ new Map();
|
|
19180
19453
|
var warnedWorkerUnavailable = false;
|
|
19181
19454
|
function resolveWorkerPath() {
|
|
19182
|
-
const candidate =
|
|
19183
|
-
return
|
|
19455
|
+
const candidate = join21(MODULE_DIR, WORKER_FILENAME);
|
|
19456
|
+
return existsSync12(candidate) ? candidate : null;
|
|
19184
19457
|
}
|
|
19185
19458
|
function pdfWorkerEnabled() {
|
|
19186
19459
|
if (process.env["PI_RESEARCH_PDF_WORKER"] === "off") return false;
|
|
@@ -19344,7 +19617,7 @@ var FETCH_RETRY_DELAY_MS = 250;
|
|
|
19344
19617
|
var playwrightAvailable = false;
|
|
19345
19618
|
var markdownConverterPromise = null;
|
|
19346
19619
|
function initScraperDependencies() {
|
|
19347
|
-
playwrightAvailable = checkModule("playwright-core") && checkModule("camoufox
|
|
19620
|
+
playwrightAvailable = checkModule("playwright-core") && checkModule("@camoufox/camoufox");
|
|
19348
19621
|
}
|
|
19349
19622
|
initScraperDependencies();
|
|
19350
19623
|
async function getMarkdownConverter() {
|
|
@@ -24080,6 +24353,18 @@ function isUnretriableResearcherError(error) {
|
|
|
24080
24353
|
const message = (error instanceof Error ? error.message : String(error)).toLowerCase();
|
|
24081
24354
|
return message.includes("requires more credits") || message.includes("insufficient credits") || message.includes("insufficient_quota");
|
|
24082
24355
|
}
|
|
24356
|
+
function isInFlightBudgetError(error) {
|
|
24357
|
+
const lower = (error instanceof Error ? error.message : String(error)).toLowerCase();
|
|
24358
|
+
return lower.includes("in_flight_budget_exhausted") || lower.includes("would exceed your available credits given your current in-flight requests");
|
|
24359
|
+
}
|
|
24360
|
+
function parseRetryAfterMs(error) {
|
|
24361
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
24362
|
+
const m = /["']?retry-after["']?\s*[:=]\s*["']?(\d{1,4})/i.exec(message);
|
|
24363
|
+
if (!m) return null;
|
|
24364
|
+
const seconds = Number(m[1]);
|
|
24365
|
+
if (!Number.isFinite(seconds) || seconds <= 0) return null;
|
|
24366
|
+
return Math.min(Math.max(seconds, 5), 60) * 1e3;
|
|
24367
|
+
}
|
|
24083
24368
|
async function runResearcher(options) {
|
|
24084
24369
|
const {
|
|
24085
24370
|
config: researcherConfig,
|
|
@@ -24172,6 +24457,7 @@ ${effectiveInitialLinks.map((l) => `- ${l}`).join("\n")}`;
|
|
|
24172
24457
|
const researcherExecutionStartMs = Date.now();
|
|
24173
24458
|
const deliveredSteeringIds = /* @__PURE__ */ new Set();
|
|
24174
24459
|
const researcherSystemPrompt = injectCurrentDate(researcherPromptTemplate, "researcher").replace("{{extra_tool_guidelines}}", "").replaceAll("{{max_scrape_urls}}", String(config.MAX_SCRAPE_URLS)).replace("{{digest_section}}", () => RESEARCHER_DIGEST_SECTION).trim();
|
|
24460
|
+
let inFlightBackoffMs = null;
|
|
24175
24461
|
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
24176
24462
|
if (signal?.aborted) throw new Error("Aborted");
|
|
24177
24463
|
if (stopRequested()) {
|
|
@@ -24179,7 +24465,8 @@ ${effectiveInitialLinks.map((l) => `- ${l}`).join("\n")}`;
|
|
|
24179
24465
|
return;
|
|
24180
24466
|
}
|
|
24181
24467
|
if (attempt > 1) {
|
|
24182
|
-
const delay2 = Math.min(1e3 * Math.pow(2, attempt - 2), config.RESEARCHER_MAX_RETRY_DELAY_MS);
|
|
24468
|
+
const delay2 = inFlightBackoffMs ?? Math.min(1e3 * Math.pow(2, attempt - 2), config.RESEARCHER_MAX_RETRY_DELAY_MS);
|
|
24469
|
+
inFlightBackoffMs = null;
|
|
24183
24470
|
logger.warn(`[ResearcherExecutor] Researcher ${id} retry ${attempt - 1}/${config.RESEARCHER_MAX_RETRIES} after ${delay2}ms`);
|
|
24184
24471
|
observer?.onResearcherProgress?.(id, "retry");
|
|
24185
24472
|
await new Promise((resolve9) => {
|
|
@@ -24422,6 +24709,11 @@ ${responseText}`);
|
|
|
24422
24709
|
metrics.increment("researcher_unretriable_total", 1, { mode: "deep", complexity: String(complexity) });
|
|
24423
24710
|
break;
|
|
24424
24711
|
}
|
|
24712
|
+
if (isInFlightBudgetError(err) && attempt < maxAttempts) {
|
|
24713
|
+
inFlightBackoffMs = parseRetryAfterMs(err) ?? 15e3;
|
|
24714
|
+
metrics.increment("researcher_inflight_budget_backoff_total", 1, { mode: "deep", complexity: String(complexity) });
|
|
24715
|
+
logger.warn(`[ResearcherExecutor] Researcher ${id} hit the provider's in-flight budget; backing off ${inFlightBackoffMs}ms (provider Retry-After, else 15s) before attempt ${attempt + 1}`);
|
|
24716
|
+
}
|
|
24425
24717
|
if (attempt < maxAttempts) {
|
|
24426
24718
|
logger.warn(`[ResearcherExecutor] Researcher ${id} attempt ${attempt} failed: ${errMsg}; will retry`);
|
|
24427
24719
|
} else {
|
|
@@ -27102,9 +27394,9 @@ init_url_utils();
|
|
|
27102
27394
|
// src/utils/research-export.ts
|
|
27103
27395
|
init_logger();
|
|
27104
27396
|
init_constants();
|
|
27105
|
-
import { promises as
|
|
27106
|
-
import { homedir as
|
|
27107
|
-
import { join as
|
|
27397
|
+
import { promises as fs16 } from "node:fs";
|
|
27398
|
+
import { homedir as homedir6, tmpdir as tmpdir4, platform as platform4 } from "node:os";
|
|
27399
|
+
import { join as join22, resolve as resolve5, normalize, sep as sep2 } from "node:path";
|
|
27108
27400
|
import { randomBytes as randomBytes6 } from "node:crypto";
|
|
27109
27401
|
var PREFERRED_SUBDIRS = [
|
|
27110
27402
|
"research",
|
|
@@ -27146,9 +27438,9 @@ var WIN_SYSTEM_PREFIXES_LC = [
|
|
|
27146
27438
|
];
|
|
27147
27439
|
function isSafeToWriteDirectly(dir) {
|
|
27148
27440
|
const normalized = normalize(resolve5(dir));
|
|
27149
|
-
const home = normalize(resolve5(
|
|
27441
|
+
const home = normalize(resolve5(homedir6()));
|
|
27150
27442
|
if (normalized === home) return false;
|
|
27151
|
-
if (
|
|
27443
|
+
if (platform4() === "win32") {
|
|
27152
27444
|
if (/^[a-zA-Z]:\\?$/.test(normalized)) return false;
|
|
27153
27445
|
const lc = normalized.toLowerCase();
|
|
27154
27446
|
for (const prefix of WIN_SYSTEM_PREFIXES_LC) {
|
|
@@ -27168,16 +27460,16 @@ function isSafeToWriteDirectly(dir) {
|
|
|
27168
27460
|
async function findPreferredSubdir(cwd) {
|
|
27169
27461
|
let entries;
|
|
27170
27462
|
try {
|
|
27171
|
-
entries = await
|
|
27463
|
+
entries = await fs16.readdir(cwd);
|
|
27172
27464
|
} catch {
|
|
27173
27465
|
return null;
|
|
27174
27466
|
}
|
|
27175
27467
|
for (const target of PREFERRED_SUBDIRS) {
|
|
27176
27468
|
const match = entries.find((e) => e.toLowerCase() === target);
|
|
27177
27469
|
if (match === void 0) continue;
|
|
27178
|
-
const candidate =
|
|
27470
|
+
const candidate = join22(cwd, match);
|
|
27179
27471
|
try {
|
|
27180
|
-
const stat6 = await
|
|
27472
|
+
const stat6 = await fs16.stat(candidate);
|
|
27181
27473
|
if (stat6.isDirectory()) return candidate;
|
|
27182
27474
|
} catch {
|
|
27183
27475
|
}
|
|
@@ -27209,7 +27501,7 @@ async function exportResearchReport(query, result, _mode, cwd, explicitDir) {
|
|
|
27209
27501
|
if (explicitDir && explicitDir.trim()) {
|
|
27210
27502
|
targetDir = resolve5(explicitDir.trim());
|
|
27211
27503
|
try {
|
|
27212
|
-
await
|
|
27504
|
+
await fs16.mkdir(targetDir, { recursive: true });
|
|
27213
27505
|
} catch (error) {
|
|
27214
27506
|
logger.error(`[export] Could not create export dir "${targetDir}":`, error);
|
|
27215
27507
|
return null;
|
|
@@ -27220,9 +27512,9 @@ async function exportResearchReport(query, result, _mode, cwd, explicitDir) {
|
|
|
27220
27512
|
for (let attempt = 0; attempt < MAX_EXPORT_RETRIES; attempt++) {
|
|
27221
27513
|
const hash = generateHash();
|
|
27222
27514
|
const filename = `${baseFilename}-${hash}.md`;
|
|
27223
|
-
const filepath =
|
|
27515
|
+
const filepath = join22(targetDir, filename);
|
|
27224
27516
|
try {
|
|
27225
|
-
await
|
|
27517
|
+
await fs16.writeFile(filepath, result, { flag: "wx", mode: 384 });
|
|
27226
27518
|
logger.log(`[export] Research report saved to: ${filepath}`);
|
|
27227
27519
|
return filepath;
|
|
27228
27520
|
} catch (error) {
|
|
@@ -27256,7 +27548,7 @@ import { getAgentDir as getAgentDir2 } from "@earendil-works/pi-coding-agent";
|
|
|
27256
27548
|
function readPkgVersion() {
|
|
27257
27549
|
try {
|
|
27258
27550
|
const pkgPath = fileURLToPath6(new URL("../package.json", import.meta.url));
|
|
27259
|
-
const pkg = JSON.parse(
|
|
27551
|
+
const pkg = JSON.parse(fs18.readFileSync(pkgPath, "utf-8"));
|
|
27260
27552
|
return typeof pkg.version === "string" && pkg.version ? pkg.version : "0.0.0";
|
|
27261
27553
|
} catch {
|
|
27262
27554
|
return "0.0.0";
|
|
@@ -27298,7 +27590,7 @@ var EXIT = {
|
|
|
27298
27590
|
CANCELLED: 130
|
|
27299
27591
|
};
|
|
27300
27592
|
function exitCodeForSignal(sig) {
|
|
27301
|
-
const signum =
|
|
27593
|
+
const signum = os8.constants.signals[sig];
|
|
27302
27594
|
return typeof signum === "number" ? 128 + signum : EXIT.CANCELLED;
|
|
27303
27595
|
}
|
|
27304
27596
|
function toStderr(s) {
|
|
@@ -27389,11 +27681,11 @@ function bridgeConfigEnv(explicitConfigPath) {
|
|
|
27389
27681
|
for (const filePath of layers) {
|
|
27390
27682
|
const isExplicit = explicitConfigPath !== void 0 && filePath === explicitConfigPath;
|
|
27391
27683
|
try {
|
|
27392
|
-
if (!
|
|
27684
|
+
if (!fs18.existsSync(filePath)) {
|
|
27393
27685
|
if (isExplicit) throw new ConfigFileError(`--config file not found: ${filePath}`);
|
|
27394
27686
|
continue;
|
|
27395
27687
|
}
|
|
27396
|
-
const parsed = parseDotEnv(
|
|
27688
|
+
const parsed = parseDotEnv(fs18.readFileSync(filePath, "utf-8"));
|
|
27397
27689
|
Object.assign(merged, parsed);
|
|
27398
27690
|
if (isExplicit) {
|
|
27399
27691
|
for (const k of Object.keys(parsed)) fromExplicitBase.add(k);
|
|
@@ -27424,14 +27716,14 @@ function resolvedConfigPaths() {
|
|
|
27424
27716
|
configDir,
|
|
27425
27717
|
cliIfaceEnv: getInterfaceEnvFilePath("cli"),
|
|
27426
27718
|
piIfaceEnv: getInterfaceEnvFilePath("pi"),
|
|
27427
|
-
piAuth:
|
|
27428
|
-
piModels:
|
|
27719
|
+
piAuth: path20.join(agentDir, "auth.json"),
|
|
27720
|
+
piModels: path20.join(agentDir, "models.json"),
|
|
27429
27721
|
piState: getStateDir()
|
|
27430
27722
|
};
|
|
27431
27723
|
}
|
|
27432
27724
|
function fileExists(p) {
|
|
27433
27725
|
try {
|
|
27434
|
-
return
|
|
27726
|
+
return fs18.existsSync(p);
|
|
27435
27727
|
} catch {
|
|
27436
27728
|
return false;
|
|
27437
27729
|
}
|
|
@@ -27727,15 +28019,12 @@ async function cmdStatus(json) {
|
|
|
27727
28019
|
installed: browserInstalled,
|
|
27728
28020
|
nativeDepsOk: nativeDeps.ok,
|
|
27729
28021
|
nativeDepsError: nativeDeps.ok ? null : nativeDeps.error,
|
|
27730
|
-
//
|
|
27731
|
-
//
|
|
27732
|
-
//
|
|
27733
|
-
|
|
27734
|
-
// binding here means an old/broken install, not an approval problem:
|
|
27735
|
-
// point at the upgrade that fixes it.
|
|
27736
|
-
nativeDepsFix: nativeDeps.ok ? null : "Upgrade npm and reinstall: npm install -g npm@12, then reinstall @lincoln504/pi-research. better-sqlite3 13 ships prebuilt bindings (no script needed); older npm needlessly recompiles it and fails on machines without a C++ toolchain.",
|
|
28022
|
+
// impit ships its binding as a per-platform optional package, no install script
|
|
28023
|
+
// involved: a missing binding means npm dropped that package, and a reinstall
|
|
28024
|
+
// restores it.
|
|
28025
|
+
nativeDepsFix: nativeDeps.ok ? null : "Reinstall @lincoln504/pi-research (npm install). The browser launcher's native module (impit) ships as a separate platform package that npm sometimes drops (npm/cli#4828); a fresh install restores it.",
|
|
27737
28026
|
// Same remedy the health check prints, so both surfaces name one command.
|
|
27738
|
-
fix: browserInstalled ? null : "npx camoufox
|
|
28027
|
+
fix: browserInstalled ? null : "npx camoufox fetch",
|
|
27739
28028
|
note: browserInstalled ? null : "Stealth browser not installed \u2014 scraping falls back to plain fetch, which some sites block. npm 12 blocks install scripts by default, which skips this package's postinstall; the engine is fetched on first use instead."
|
|
27740
28029
|
},
|
|
27741
28030
|
knowledgeStoreMode: cfg.KNOWLEDGE_STORE_MODE,
|
|
@@ -27847,7 +28136,7 @@ change the machine-wide defaults: set PI_RESEARCH_KNOWLEDGE_STORE_MODE=<mode> an
|
|
|
27847
28136
|
}
|
|
27848
28137
|
function cmdCleanup(c) {
|
|
27849
28138
|
const script = fileURLToPath6(new URL("../scripts/cleanup.cjs", import.meta.url));
|
|
27850
|
-
if (!
|
|
28139
|
+
if (!fs18.existsSync(script)) {
|
|
27851
28140
|
toStderr(
|
|
27852
28141
|
`
|
|
27853
28142
|
Error: cleanup script not found at ${script}.
|
|
@@ -28126,7 +28415,8 @@ COMMANDS
|
|
|
28126
28415
|
state tree (<config dir>/research/state) and the cache, including
|
|
28127
28416
|
downloaded embedding models. Keeps config.env and the knowledge
|
|
28128
28417
|
store database. --purge-browsers also deletes the shared
|
|
28129
|
-
stealth-browser cache (~/.cache/camoufox
|
|
28418
|
+
stealth-browser cache (~/.cache/camoufox, every installed
|
|
28419
|
+
browser build) other tools use.
|
|
28130
28420
|
|
|
28131
28421
|
help, --help, -h Show this help.
|
|
28132
28422
|
--version, -v Print the pi-research version.
|
|
@@ -28520,8 +28810,8 @@ Error: ${err.message}
|
|
|
28520
28810
|
var _isMain = (() => {
|
|
28521
28811
|
try {
|
|
28522
28812
|
if (!process.argv[1]) return false;
|
|
28523
|
-
const modulePath =
|
|
28524
|
-
const invokedPath =
|
|
28813
|
+
const modulePath = fs18.realpathSync(path20.resolve(fileURLToPath6(import.meta.url)));
|
|
28814
|
+
const invokedPath = fs18.realpathSync(path20.resolve(process.argv[1]));
|
|
28525
28815
|
return modulePath === invokedPath;
|
|
28526
28816
|
} catch {
|
|
28527
28817
|
return false;
|