@lincoln504/pi-research 1.4.1 → 1.4.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.mjs +271 -156
- package/dist/thread-worker.mjs +91 -59
- package/docs/CHANGELOG.md +76 -0
- package/docs/CONFIGURATION.md +1 -1
- package/package.json +1 -1
- package/skills/pi-research/SKILL.md +1 -1
- package/src/config.ts +10 -4
- package/src/core/planning-service.ts +55 -8
- package/src/infrastructure/browser/browser-task-scheduler.ts +16 -9
- package/src/infrastructure/browser/config.ts +40 -2
- package/src/infrastructure/browser/thread-worker-browser.ts +69 -48
- package/src/infrastructure/browser/thread-worker-messaging.ts +61 -4
- package/src/infrastructure/browser/thread-worker.mjs +91 -59
- package/src/infrastructure/browser/thread-worker.ts +13 -5
- package/src/infrastructure/embedding/embedding-server.ts +34 -0
- package/src/knowledge/embedder-init.ts +3 -2
- package/src/knowledge/embedder.ts +11 -3
- package/src/knowledge/index.ts +36 -10
- package/src/knowledge/store.ts +29 -5
- package/src/orchestration/deep-research-orchestrator.ts +3 -1
- package/src/orchestration/session-state.ts +16 -0
- package/src/sdk.ts +10 -0
- package/src/skill-install/skill-installer.ts +5 -1
- package/src/stackexchange/index.ts +3 -0
- package/src/stackexchange/rest-client.ts +7 -12
- package/src/tools/research-tool-definition.ts +38 -7
- package/src/tools/security.ts +13 -10
- package/src/utils/coverage-digest.ts +1 -1
- package/src/utils/host-config.ts +12 -1
- package/src/utils/log-utils.ts +10 -0
- package/src/web-research/browser-search.ts +11 -4
- package/src/web-research/web-scraper.ts +27 -16
- package/src/youtube/potoken.ts +8 -1
- package/src/youtube/transcript-client.ts +56 -5
package/dist/cli.mjs
CHANGED
|
@@ -241,6 +241,8 @@ function redactSecrets(message) {
|
|
|
241
241
|
}
|
|
242
242
|
if (out.length > MAX_LOG_MESSAGE_LENGTH) {
|
|
243
243
|
out = `${out.slice(0, keep)}\u2026[truncated ${Math.max(rawLength, out.length) - keep} chars]`;
|
|
244
|
+
} else if (rawLength > scanEnd) {
|
|
245
|
+
out = `${out}\u2026[truncated ${rawLength - scanEnd} chars]`;
|
|
244
246
|
}
|
|
245
247
|
return out;
|
|
246
248
|
}
|
|
@@ -2279,7 +2281,9 @@ import { createRequire as createRequire2 } from "node:module";
|
|
|
2279
2281
|
function getConfigDirName() {
|
|
2280
2282
|
if (_configDirName !== void 0) return _configDirName;
|
|
2281
2283
|
const override = process.env["PI_RESEARCH_CONFIG_DIR_NAME"];
|
|
2282
|
-
if (override)
|
|
2284
|
+
if (override && !override.includes("/") && !override.includes("\\") && override !== "." && override !== "..") {
|
|
2285
|
+
return _configDirName = override;
|
|
2286
|
+
}
|
|
2283
2287
|
try {
|
|
2284
2288
|
const require2 = createRequire2(import.meta.url);
|
|
2285
2289
|
const host = require2("@earendil-works/pi-coding-agent");
|
|
@@ -3031,10 +3035,16 @@ var init_config = __esm({
|
|
|
3031
3035
|
SEARCH_TIMEOUT_MS: Type.Number({ minimum: 5e3, maximum: 12e4, default: 45e3 }),
|
|
3032
3036
|
/** TUI refresh debounce in milliseconds (default: 100ms) */
|
|
3033
3037
|
TUI_REFRESH_DEBOUNCE_MS: Type.Number({ minimum: 0, maximum: 1e3, default: 100 }),
|
|
3034
|
-
/**
|
|
3035
|
-
*
|
|
3036
|
-
* SEARCH_TIMEOUT_MS + this, a scrape after SCRAPE_TIMEOUT_MS + this
|
|
3037
|
-
*
|
|
3038
|
+
/** Overhead margin (ms) added on top of each browser operation's own nav timeout to
|
|
3039
|
+
* form its scheduler task-timeout ceiling: a search task times out after
|
|
3040
|
+
* SEARCH_TIMEOUT_MS + this, a scrape after SCRAPE_TIMEOUT_MS + this — covering
|
|
3041
|
+
* per-task work beyond raw navigation (page setup, result parsing). Queue wait is
|
|
3042
|
+
* NOT part of this margin: the deadline is armed on dispatch, not on enqueue, so
|
|
3043
|
+
* time spent waiting for a free worker never counts against it (see
|
|
3044
|
+
* browser-task-scheduler.ts). Cold browser-launch/context-creation time is also
|
|
3045
|
+
* separate from this margin — COLD_START_ALLOWANCE_MS
|
|
3046
|
+
* (infrastructure/browser/config.ts) covers that unconditionally on top of it,
|
|
3047
|
+
* since it isn't something a user should need to tune.
|
|
3038
3048
|
* (default: 10000) */
|
|
3039
3049
|
BROWSER_TASK_TIMEOUT_MS: Type.Number({ minimum: 2e3, maximum: 12e4, default: 1e4 }),
|
|
3040
3050
|
/** Timeout for coordinator/research-lead/repair/knowledge LLM calls in ms (default: 300000 = 5 min, range: 60s-30min).
|
|
@@ -4330,12 +4340,12 @@ async function isModelCached(model) {
|
|
|
4330
4340
|
const cacheDir = env.cacheDir;
|
|
4331
4341
|
if (!cacheDir) return false;
|
|
4332
4342
|
const { access: access3, readdir: readdir6, readFile: readFile7, stat: stat6, rm: rm3 } = await import("node:fs/promises");
|
|
4333
|
-
const
|
|
4334
|
-
const modelDir =
|
|
4335
|
-
const onnxDir =
|
|
4336
|
-
await access3(
|
|
4343
|
+
const path20 = await import("node:path");
|
|
4344
|
+
const modelDir = path20.default.join(cacheDir, model);
|
|
4345
|
+
const onnxDir = path20.default.join(modelDir, "onnx");
|
|
4346
|
+
await access3(path20.default.join(onnxDir, "model.onnx"));
|
|
4337
4347
|
try {
|
|
4338
|
-
const cfgRaw = await readFile7(
|
|
4348
|
+
const cfgRaw = await readFile7(path20.default.join(modelDir, "config.json"), "utf-8");
|
|
4339
4349
|
JSON.parse(cfgRaw);
|
|
4340
4350
|
} catch {
|
|
4341
4351
|
return false;
|
|
@@ -4343,7 +4353,7 @@ async function isModelCached(model) {
|
|
|
4343
4353
|
const tokenizerFiles = ["tokenizer.json", "tokenizer.model", "vocab.txt"];
|
|
4344
4354
|
let hasTokenizer = false;
|
|
4345
4355
|
for (const f of tokenizerFiles) {
|
|
4346
|
-
const st = await stat6(
|
|
4356
|
+
const st = await stat6(path20.default.join(modelDir, f)).catch(() => null);
|
|
4347
4357
|
if (st && st.size > 0) {
|
|
4348
4358
|
hasTokenizer = true;
|
|
4349
4359
|
break;
|
|
@@ -4353,7 +4363,7 @@ async function isModelCached(model) {
|
|
|
4353
4363
|
const entries = await readdir6(onnxDir).catch(() => []);
|
|
4354
4364
|
const hasExternalWeights = entries.includes("model.onnx_data");
|
|
4355
4365
|
if (hasExternalWeights) {
|
|
4356
|
-
const dataStat = await stat6(
|
|
4366
|
+
const dataStat = await stat6(path20.default.join(onnxDir, "model.onnx_data")).catch(() => null);
|
|
4357
4367
|
if (!dataStat || dataStat.size === 0) return false;
|
|
4358
4368
|
}
|
|
4359
4369
|
const partialArtifacts = entries.filter(
|
|
@@ -4362,7 +4372,7 @@ async function isModelCached(model) {
|
|
|
4362
4372
|
if (partialArtifacts.length > 0) {
|
|
4363
4373
|
if (hasExternalWeights) {
|
|
4364
4374
|
for (const f of partialArtifacts) {
|
|
4365
|
-
await rm3(
|
|
4375
|
+
await rm3(path20.default.join(onnxDir, f), { force: true }).catch(() => {
|
|
4366
4376
|
});
|
|
4367
4377
|
}
|
|
4368
4378
|
} else {
|
|
@@ -4384,10 +4394,10 @@ async function purgeModelCache(model) {
|
|
|
4384
4394
|
const cacheDir = env.cacheDir;
|
|
4385
4395
|
if (!cacheDir) return;
|
|
4386
4396
|
const { rm: rm3 } = await import("node:fs/promises");
|
|
4387
|
-
const
|
|
4388
|
-
const resolvedCache =
|
|
4389
|
-
const modelDir =
|
|
4390
|
-
if (!model ||
|
|
4397
|
+
const path20 = await import("node:path");
|
|
4398
|
+
const resolvedCache = path20.default.resolve(cacheDir);
|
|
4399
|
+
const modelDir = path20.default.resolve(resolvedCache, model);
|
|
4400
|
+
if (!model || path20.default.isAbsolute(model) || modelDir === resolvedCache || !modelDir.startsWith(resolvedCache + path20.default.sep)) {
|
|
4391
4401
|
logger.warn(`[embedder] Refusing to purge unsafe model-cache path for model id ${JSON.stringify(model)}`);
|
|
4392
4402
|
return;
|
|
4393
4403
|
}
|
|
@@ -4470,7 +4480,7 @@ async function handleWebGPULoadError(loadErr, pipeline, stateManager, gpuLockHel
|
|
|
4470
4480
|
return { success: false, error };
|
|
4471
4481
|
}
|
|
4472
4482
|
}
|
|
4473
|
-
async function handleWebGPUWarmupError(warmupErr, pipeline, stateManager, gpuLockHeld, model, initializationTimeoutMs, useCache) {
|
|
4483
|
+
async function handleWebGPUWarmupError(warmupErr, pipeline, stateManager, gpuLockHeld, model, initializationTimeoutMs, useCache, poolingMode = "mean") {
|
|
4474
4484
|
markWebGpuFallback();
|
|
4475
4485
|
logger.warn(
|
|
4476
4486
|
"[embedder] WebGPU error during warmup \u2014 falling back to CPU. The underlying cause (commonly VRAM exhaustion) is in the native Dawn/ONNX output logged just above this line."
|
|
@@ -4487,7 +4497,7 @@ async function handleWebGPUWarmupError(warmupErr, pipeline, stateManager, gpuLoc
|
|
|
4487
4497
|
try {
|
|
4488
4498
|
const cpuPipeline = await loadModelOnCPU(model, initializationTimeoutMs, useCache);
|
|
4489
4499
|
logger.info("[embedder] CPU pipeline loaded successfully");
|
|
4490
|
-
const { dummy, success, error: cpuWarmupErr } = await warmupPipeline(cpuPipeline,
|
|
4500
|
+
const { dummy, success, error: cpuWarmupErr } = await warmupPipeline(cpuPipeline, poolingMode, useCache);
|
|
4491
4501
|
if (!success || cpuWarmupErr) {
|
|
4492
4502
|
logger.error("[embedder] CPU fallback pipeline warmup failed:", cpuWarmupErr);
|
|
4493
4503
|
if (cpuPipeline) {
|
|
@@ -4906,7 +4916,8 @@ var init_embedder = __esm({
|
|
|
4906
4916
|
this.gpuLockHeld,
|
|
4907
4917
|
this.model,
|
|
4908
4918
|
this.initializationTimeoutMs,
|
|
4909
|
-
this.useCache
|
|
4919
|
+
this.useCache,
|
|
4920
|
+
this.poolingMode
|
|
4910
4921
|
);
|
|
4911
4922
|
if (!result.success) {
|
|
4912
4923
|
throw result.error;
|
|
@@ -4968,9 +4979,12 @@ var init_embedder = __esm({
|
|
|
4968
4979
|
truncateText(text) {
|
|
4969
4980
|
const maxChars = this.maxTokens * this.charsPerToken;
|
|
4970
4981
|
if (text.length > maxChars) {
|
|
4982
|
+
let keep = maxChars;
|
|
4983
|
+
const lastKeptUnit = text.charCodeAt(keep - 1);
|
|
4984
|
+
if (lastKeptUnit >= 55296 && lastKeptUnit <= 56319) keep -= 1;
|
|
4971
4985
|
metrics.increment("embedder_truncations_total", 1, { model: this.model });
|
|
4972
|
-
logger.debug(`[embedder] Truncated input for embedding: ${text.length} -> ${
|
|
4973
|
-
return text.slice(0,
|
|
4986
|
+
logger.debug(`[embedder] Truncated input for embedding: ${text.length} -> ${keep} chars (model=${this.model})`);
|
|
4987
|
+
return text.slice(0, keep);
|
|
4974
4988
|
}
|
|
4975
4989
|
return text;
|
|
4976
4990
|
}
|
|
@@ -5432,6 +5446,11 @@ var init_embedding_server = __esm({
|
|
|
5432
5446
|
hardDeadlineMs;
|
|
5433
5447
|
onPoison;
|
|
5434
5448
|
poisoned = null;
|
|
5449
|
+
// The currently-executing task's real work promise (not the soft-timeout race) —
|
|
5450
|
+
// set only while a native call is actually in flight. Lets a caller that needs
|
|
5451
|
+
// to dispose the underlying pipeline (EmbeddingServer.shutdown) wait for true
|
|
5452
|
+
// idleness instead of racing disposal against a still-running inference.
|
|
5453
|
+
currentWork = null;
|
|
5435
5454
|
constructor(maxDepth = 200, timeoutMs = 12e4, hardDeadlineMs = 6e5, onPoison) {
|
|
5436
5455
|
this.maxDepth = maxDepth;
|
|
5437
5456
|
this.timeoutMs = timeoutMs;
|
|
@@ -5457,6 +5476,7 @@ var init_embedding_server = __esm({
|
|
|
5457
5476
|
let hardTimer;
|
|
5458
5477
|
try {
|
|
5459
5478
|
work = fn();
|
|
5479
|
+
this.currentWork = work;
|
|
5460
5480
|
hardTimer = setTimeout(() => {
|
|
5461
5481
|
if (settled) return;
|
|
5462
5482
|
this.poison(new Error(
|
|
@@ -5477,6 +5497,7 @@ var init_embedding_server = __esm({
|
|
|
5477
5497
|
} finally {
|
|
5478
5498
|
settled = true;
|
|
5479
5499
|
if (hardTimer !== void 0) clearTimeout(hardTimer);
|
|
5500
|
+
if (this.currentWork === work) this.currentWork = null;
|
|
5480
5501
|
}
|
|
5481
5502
|
}
|
|
5482
5503
|
};
|
|
@@ -5484,6 +5505,23 @@ var init_embedding_server = __esm({
|
|
|
5484
5505
|
if (!this.running) void this.pump();
|
|
5485
5506
|
});
|
|
5486
5507
|
}
|
|
5508
|
+
/**
|
|
5509
|
+
* Resolve once no native call is in flight. Used by EmbeddingServer.shutdown()
|
|
5510
|
+
* to wait out a still-running inference before disposing the pipeline —
|
|
5511
|
+
* without this, disposal could run concurrently with a legitimately slow (not
|
|
5512
|
+
* hung) embed/embedMany call on the same re-entrant-locked GPU context, the
|
|
5513
|
+
* exact native-segfault class this queue exists to prevent. No timeout: a
|
|
5514
|
+
* promise that truly never settles is already an accepted, pre-existing
|
|
5515
|
+
* leak-to-process-exit scenario elsewhere in this class (see the poison path's
|
|
5516
|
+
* disposeEmbedder=false handling) — better to leak than risk concurrent
|
|
5517
|
+
* native access.
|
|
5518
|
+
*/
|
|
5519
|
+
async waitForIdle() {
|
|
5520
|
+
while (this.currentWork) {
|
|
5521
|
+
await this.currentWork.catch(() => {
|
|
5522
|
+
});
|
|
5523
|
+
}
|
|
5524
|
+
}
|
|
5487
5525
|
poison(err) {
|
|
5488
5526
|
if (this.poisoned) return;
|
|
5489
5527
|
this.poisoned = err;
|
|
@@ -5665,6 +5703,7 @@ var init_embedding_server = __esm({
|
|
|
5665
5703
|
this.server = null;
|
|
5666
5704
|
}
|
|
5667
5705
|
if (disposeEmbedder) {
|
|
5706
|
+
await this.queue.waitForIdle();
|
|
5668
5707
|
await this.embedder.dispose().catch((err) => {
|
|
5669
5708
|
if (this.isShuttingDown) {
|
|
5670
5709
|
logger.debug("[EmbeddingServer] Error disposing embedder during shutdown (expected):", err);
|
|
@@ -5895,7 +5934,7 @@ var init_embedding_client = __esm({
|
|
|
5895
5934
|
}
|
|
5896
5935
|
}
|
|
5897
5936
|
// ---- Internal HTTP helpers ----
|
|
5898
|
-
getRequest(
|
|
5937
|
+
getRequest(path20, timeoutMs = 12e4) {
|
|
5899
5938
|
return new Promise((resolve9, reject) => {
|
|
5900
5939
|
let resolved = false;
|
|
5901
5940
|
let req;
|
|
@@ -5903,7 +5942,7 @@ var init_embedding_client = __esm({
|
|
|
5903
5942
|
if (!resolved) {
|
|
5904
5943
|
resolved = true;
|
|
5905
5944
|
req?.destroy();
|
|
5906
|
-
reject(new Error(`[EmbeddingClient] GET ${
|
|
5945
|
+
reject(new Error(`[EmbeddingClient] GET ${path20} timed out after ${timeoutMs}ms`));
|
|
5907
5946
|
}
|
|
5908
5947
|
}, timeoutMs);
|
|
5909
5948
|
if (timer.unref) timer.unref();
|
|
@@ -5911,7 +5950,7 @@ var init_embedding_client = __esm({
|
|
|
5911
5950
|
{
|
|
5912
5951
|
hostname: "127.0.0.1",
|
|
5913
5952
|
port: this.port,
|
|
5914
|
-
path:
|
|
5953
|
+
path: path20,
|
|
5915
5954
|
method: "GET",
|
|
5916
5955
|
headers: this.authHeaders()
|
|
5917
5956
|
},
|
|
@@ -5927,19 +5966,19 @@ var init_embedding_client = __esm({
|
|
|
5927
5966
|
try {
|
|
5928
5967
|
const parsed = JSON.parse(collected.toString());
|
|
5929
5968
|
if (res.statusCode !== 200) {
|
|
5930
|
-
reject(new Error(`[EmbeddingClient] GET ${
|
|
5969
|
+
reject(new Error(`[EmbeddingClient] GET ${path20} returned HTTP ${res.statusCode}`));
|
|
5931
5970
|
} else {
|
|
5932
5971
|
resolve9(parsed);
|
|
5933
5972
|
}
|
|
5934
5973
|
} catch (_e) {
|
|
5935
|
-
reject(new Error(`[EmbeddingClient] Failed to parse GET ${
|
|
5974
|
+
reject(new Error(`[EmbeddingClient] Failed to parse GET ${path20} response: ${collected.toString()}`));
|
|
5936
5975
|
}
|
|
5937
5976
|
});
|
|
5938
5977
|
res.on("error", (err) => {
|
|
5939
5978
|
if (resolved) return;
|
|
5940
5979
|
resolved = true;
|
|
5941
5980
|
clearTimeout(timer);
|
|
5942
|
-
reject(new Error(`[EmbeddingClient] GET ${
|
|
5981
|
+
reject(new Error(`[EmbeddingClient] GET ${path20} response error: ${err.message}`));
|
|
5943
5982
|
});
|
|
5944
5983
|
}
|
|
5945
5984
|
);
|
|
@@ -5947,12 +5986,12 @@ var init_embedding_client = __esm({
|
|
|
5947
5986
|
if (resolved) return;
|
|
5948
5987
|
resolved = true;
|
|
5949
5988
|
clearTimeout(timer);
|
|
5950
|
-
reject(new Error(`[EmbeddingClient] GET ${
|
|
5989
|
+
reject(new Error(`[EmbeddingClient] GET ${path20} request error: ${err.message}`));
|
|
5951
5990
|
});
|
|
5952
5991
|
req.end();
|
|
5953
5992
|
});
|
|
5954
5993
|
}
|
|
5955
|
-
request(
|
|
5994
|
+
request(path20, data) {
|
|
5956
5995
|
return new Promise((resolve9, reject) => {
|
|
5957
5996
|
const timeoutMs = 12e4;
|
|
5958
5997
|
let resolved = false;
|
|
@@ -5961,7 +6000,7 @@ var init_embedding_client = __esm({
|
|
|
5961
6000
|
if (!resolved) {
|
|
5962
6001
|
resolved = true;
|
|
5963
6002
|
req?.destroy();
|
|
5964
|
-
reject(new Error(`[EmbeddingClient] POST ${
|
|
6003
|
+
reject(new Error(`[EmbeddingClient] POST ${path20} timed out after ${timeoutMs}ms`));
|
|
5965
6004
|
}
|
|
5966
6005
|
}, timeoutMs);
|
|
5967
6006
|
if (timer.unref) timer.unref();
|
|
@@ -5970,7 +6009,7 @@ var init_embedding_client = __esm({
|
|
|
5970
6009
|
{
|
|
5971
6010
|
hostname: "127.0.0.1",
|
|
5972
6011
|
port: this.port,
|
|
5973
|
-
path:
|
|
6012
|
+
path: path20,
|
|
5974
6013
|
method: "POST",
|
|
5975
6014
|
headers: {
|
|
5976
6015
|
"Content-Type": "application/json",
|
|
@@ -5996,14 +6035,14 @@ var init_embedding_client = __esm({
|
|
|
5996
6035
|
resolve9(parsed);
|
|
5997
6036
|
}
|
|
5998
6037
|
} catch (_e) {
|
|
5999
|
-
reject(new Error(`[EmbeddingClient] Failed to parse POST ${
|
|
6038
|
+
reject(new Error(`[EmbeddingClient] Failed to parse POST ${path20} response: ${collected.toString()}`));
|
|
6000
6039
|
}
|
|
6001
6040
|
});
|
|
6002
6041
|
res.on("error", (err) => {
|
|
6003
6042
|
if (resolved) return;
|
|
6004
6043
|
resolved = true;
|
|
6005
6044
|
clearTimeout(timer);
|
|
6006
|
-
reject(new Error(`[EmbeddingClient] POST ${
|
|
6045
|
+
reject(new Error(`[EmbeddingClient] POST ${path20} response error: ${err.message}`));
|
|
6007
6046
|
});
|
|
6008
6047
|
}
|
|
6009
6048
|
);
|
|
@@ -6011,7 +6050,7 @@ var init_embedding_client = __esm({
|
|
|
6011
6050
|
if (resolved) return;
|
|
6012
6051
|
resolved = true;
|
|
6013
6052
|
clearTimeout(timer);
|
|
6014
|
-
reject(new Error(`[EmbeddingClient] POST ${
|
|
6053
|
+
reject(new Error(`[EmbeddingClient] POST ${path20} request error: ${err.message}`));
|
|
6015
6054
|
});
|
|
6016
6055
|
req.write(body);
|
|
6017
6056
|
req.end();
|
|
@@ -7640,8 +7679,15 @@ var init_store = __esm({
|
|
|
7640
7679
|
const canonicalDir = path15.join(this.options.dbDir, `${canonicalName}.lance`);
|
|
7641
7680
|
await fsPromises.rename(tempDir, canonicalDir);
|
|
7642
7681
|
this.tableName = canonicalName;
|
|
7643
|
-
await this.saveManifest()
|
|
7644
|
-
|
|
7682
|
+
if (await this.saveManifest()) {
|
|
7683
|
+
this.table = await this.db.openTable(this.tableName);
|
|
7684
|
+
} else {
|
|
7685
|
+
logger.error("[store] Failed to persist canonical table name after rename \u2014 reverting to keep manifest and disk in sync.");
|
|
7686
|
+
await fsPromises.rename(canonicalDir, tempDir);
|
|
7687
|
+
this.tableName = tempTableName;
|
|
7688
|
+
this.table = await this.db.openTable(this.tableName);
|
|
7689
|
+
logger.info(`[store] Migrated data remains safe and accessible as ${tempTableName}; canonical rename deferred to a future successful manifest save.`);
|
|
7690
|
+
}
|
|
7645
7691
|
} catch (renameErr) {
|
|
7646
7692
|
logger.warn(`[store] Directory rename failed, persisting temp table name in manifest: ${renameErr}`);
|
|
7647
7693
|
this.tableName = tempTableName;
|
|
@@ -8693,6 +8739,7 @@ var init_chunker = __esm({
|
|
|
8693
8739
|
|
|
8694
8740
|
// src/knowledge/index.ts
|
|
8695
8741
|
import * as fs14 from "node:fs";
|
|
8742
|
+
import * as path16 from "node:path";
|
|
8696
8743
|
function getMigrationStrategy(config) {
|
|
8697
8744
|
const strategy = config.MIGRATION_STRATEGY;
|
|
8698
8745
|
if (!strategy) return void 0;
|
|
@@ -8754,15 +8801,25 @@ async function createKnowledgeStoreComponents(embedderFactory, reconnectFactory,
|
|
|
8754
8801
|
}
|
|
8755
8802
|
async function forceDeleteKnowledgeStore(config, workspace) {
|
|
8756
8803
|
const dbDir = getDbDir(config, workspace);
|
|
8757
|
-
if (fs14.existsSync(dbDir))
|
|
8758
|
-
|
|
8759
|
-
|
|
8760
|
-
|
|
8761
|
-
|
|
8762
|
-
|
|
8763
|
-
|
|
8764
|
-
throw err;
|
|
8804
|
+
if (!fs14.existsSync(dbDir)) return;
|
|
8805
|
+
let tableName = "knowledge";
|
|
8806
|
+
const manifestPath = path16.join(dbDir, "store-manifest.json");
|
|
8807
|
+
try {
|
|
8808
|
+
if (fs14.existsSync(manifestPath)) {
|
|
8809
|
+
const manifest = JSON.parse(fs14.readFileSync(manifestPath, "utf-8"));
|
|
8810
|
+
if (manifest.activeTableName) tableName = manifest.activeTableName;
|
|
8765
8811
|
}
|
|
8812
|
+
} catch (err) {
|
|
8813
|
+
logger.warn(`[knowledge] Failed to read manifest before forced delete, defaulting to table '${tableName}':`, err);
|
|
8814
|
+
}
|
|
8815
|
+
const tableDir = path16.join(dbDir, `${tableName}.lance`);
|
|
8816
|
+
if (!fs14.existsSync(tableDir)) return;
|
|
8817
|
+
try {
|
|
8818
|
+
fs14.rmSync(tableDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 50 });
|
|
8819
|
+
logger.info(`[knowledge] Corrupted table directory ${tableDir} forcefully deleted; will be recreated on next init.`);
|
|
8820
|
+
} catch (err) {
|
|
8821
|
+
logger.error(`[knowledge] Failed to forcefully delete table directory ${tableDir}:`, err);
|
|
8822
|
+
throw err;
|
|
8766
8823
|
}
|
|
8767
8824
|
}
|
|
8768
8825
|
var MAX_INIT_RETRIES, BASE_RETRY_DELAY_MS, MAX_RETRY_DELAY_MS;
|
|
@@ -8787,7 +8844,7 @@ var knowledge_store_service_exports = {};
|
|
|
8787
8844
|
__export(knowledge_store_service_exports, {
|
|
8788
8845
|
KnowledgeStoreService: () => KnowledgeStoreService
|
|
8789
8846
|
});
|
|
8790
|
-
import * as
|
|
8847
|
+
import * as path17 from "node:path";
|
|
8791
8848
|
var KnowledgeStoreService;
|
|
8792
8849
|
var init_knowledge_store_service = __esm({
|
|
8793
8850
|
"src/infrastructure/knowledge-store-service.ts"() {
|
|
@@ -8881,7 +8938,7 @@ var init_knowledge_store_service = __esm({
|
|
|
8881
8938
|
};
|
|
8882
8939
|
const container = tryGetServiceContainerFromCtx(ctx);
|
|
8883
8940
|
const pathConfig = await getService(ServiceNames.STATE_PATH_CONFIGURATION, void 0, container);
|
|
8884
|
-
const lockPath =
|
|
8941
|
+
const lockPath = path17.join(pathConfig.getLockDirPath(), "knowledge-store-init.lock");
|
|
8885
8942
|
if (!this._initLock) {
|
|
8886
8943
|
const processLifecycle = await getService(ServiceNames.PROCESS_LIFECYCLE, void 0, container);
|
|
8887
8944
|
this._initLock = new FileLockService({
|
|
@@ -9942,11 +9999,11 @@ __export(skill_installer_exports, {
|
|
|
9942
9999
|
import * as crypto10 from "node:crypto";
|
|
9943
10000
|
import * as fs16 from "node:fs";
|
|
9944
10001
|
import * as os6 from "node:os";
|
|
9945
|
-
import * as
|
|
10002
|
+
import * as path18 from "node:path";
|
|
9946
10003
|
import { fileURLToPath as fileURLToPath4 } from "node:url";
|
|
9947
10004
|
function readSkillVersion(sourceDir) {
|
|
9948
10005
|
try {
|
|
9949
|
-
const md = fs16.readFileSync(
|
|
10006
|
+
const md = fs16.readFileSync(path18.join(sourceDir, "SKILL.md"), "utf-8");
|
|
9950
10007
|
return /"version"\s*:\s*"([^"]+)"/.exec(md)?.[1] ?? null;
|
|
9951
10008
|
} catch {
|
|
9952
10009
|
return null;
|
|
@@ -9961,7 +10018,7 @@ function resolveSkillSourceDir(explicit) {
|
|
|
9961
10018
|
if (process.env["PI_RESEARCH_SKILL_DIR"]) candidates.push(process.env["PI_RESEARCH_SKILL_DIR"]);
|
|
9962
10019
|
const fromHere = (() => {
|
|
9963
10020
|
try {
|
|
9964
|
-
return
|
|
10021
|
+
return path18.dirname(fileURLToPath4(import.meta.url));
|
|
9965
10022
|
} catch {
|
|
9966
10023
|
return void 0;
|
|
9967
10024
|
}
|
|
@@ -9970,22 +10027,22 @@ function resolveSkillSourceDir(explicit) {
|
|
|
9970
10027
|
if (!start) continue;
|
|
9971
10028
|
let dir = start;
|
|
9972
10029
|
for (let i = 0; i < 8; i++) {
|
|
9973
|
-
candidates.push(
|
|
9974
|
-
const parent =
|
|
10030
|
+
candidates.push(path18.join(dir, "skills", SKILL_NAME));
|
|
10031
|
+
const parent = path18.dirname(dir);
|
|
9975
10032
|
if (parent === dir) break;
|
|
9976
10033
|
dir = parent;
|
|
9977
10034
|
}
|
|
9978
10035
|
}
|
|
9979
10036
|
for (const c of candidates) {
|
|
9980
10037
|
try {
|
|
9981
|
-
if (fs16.existsSync(
|
|
10038
|
+
if (fs16.existsSync(path18.join(c, "SKILL.md"))) return path18.resolve(c);
|
|
9982
10039
|
} catch {
|
|
9983
10040
|
}
|
|
9984
10041
|
}
|
|
9985
10042
|
throw new Error("Could not locate the bundled research skill (skills/pi-research/SKILL.md). Set PI_RESEARCH_SKILL_DIR to its path.");
|
|
9986
10043
|
}
|
|
9987
10044
|
function getManifestPath(opts) {
|
|
9988
|
-
return
|
|
10045
|
+
return path18.join(homeDir(opts), ".pi", "research", "installed-skills.json");
|
|
9989
10046
|
}
|
|
9990
10047
|
function readManifest(opts) {
|
|
9991
10048
|
const p = getManifestPath(opts);
|
|
@@ -10002,7 +10059,7 @@ function readManifest(opts) {
|
|
|
10002
10059
|
}
|
|
10003
10060
|
function writeManifest(manifest, opts) {
|
|
10004
10061
|
const p = getManifestPath(opts);
|
|
10005
|
-
fs16.mkdirSync(
|
|
10062
|
+
fs16.mkdirSync(path18.dirname(p), { recursive: true });
|
|
10006
10063
|
const tmp = `${p}.tmp.${process.pid}.${Date.now()}.${manifestTmpSeq++}`;
|
|
10007
10064
|
fs16.writeFileSync(tmp, JSON.stringify(manifest, null, 2) + "\n", "utf-8");
|
|
10008
10065
|
try {
|
|
@@ -10024,8 +10081,8 @@ function isOwnedSymlink(targetPath, expectedSource) {
|
|
|
10024
10081
|
try {
|
|
10025
10082
|
const st = fs16.lstatSync(targetPath);
|
|
10026
10083
|
if (!st.isSymbolicLink()) return false;
|
|
10027
|
-
const dest =
|
|
10028
|
-
if (expectedSource &&
|
|
10084
|
+
const dest = path18.resolve(path18.dirname(targetPath), fs16.readlinkSync(targetPath));
|
|
10085
|
+
if (expectedSource && path18.resolve(expectedSource) === dest) return true;
|
|
10029
10086
|
return /[/\\]pi-research[/\\]skills[/\\]pi-research$/.test(dest);
|
|
10030
10087
|
} catch {
|
|
10031
10088
|
return false;
|
|
@@ -10033,7 +10090,7 @@ function isOwnedSymlink(targetPath, expectedSource) {
|
|
|
10033
10090
|
}
|
|
10034
10091
|
function isOwnedCopy(targetPath) {
|
|
10035
10092
|
try {
|
|
10036
|
-
const md = fs16.readFileSync(
|
|
10093
|
+
const md = fs16.readFileSync(path18.join(targetPath, "SKILL.md"), "utf-8");
|
|
10037
10094
|
return md.includes(PACKAGE_NAME);
|
|
10038
10095
|
} catch {
|
|
10039
10096
|
return false;
|
|
@@ -10042,11 +10099,11 @@ function isOwnedCopy(targetPath) {
|
|
|
10042
10099
|
function detectHarnesses(opts) {
|
|
10043
10100
|
const home = homeDir(opts);
|
|
10044
10101
|
return HARNESSES.map((h) => {
|
|
10045
|
-
const absSkillsDir =
|
|
10046
|
-
const absSkillPath =
|
|
10102
|
+
const absSkillsDir = path18.join(home, h.skillsDir);
|
|
10103
|
+
const absSkillPath = path18.join(absSkillsDir, SKILL_NAME);
|
|
10047
10104
|
let present = false;
|
|
10048
10105
|
try {
|
|
10049
|
-
present = fs16.existsSync(
|
|
10106
|
+
present = fs16.existsSync(path18.join(home, h.baseDir));
|
|
10050
10107
|
} catch {
|
|
10051
10108
|
}
|
|
10052
10109
|
let installed = "none";
|
|
@@ -10087,15 +10144,15 @@ function installSkill(toolIds, opts = {}) {
|
|
|
10087
10144
|
results.push({ tool: id, path: "", type: "symlink", status: "error", message: `unknown target "${id}"` });
|
|
10088
10145
|
continue;
|
|
10089
10146
|
}
|
|
10090
|
-
const absSkillsDir =
|
|
10091
|
-
const absSkillPath =
|
|
10147
|
+
const absSkillsDir = path18.join(home, def.skillsDir);
|
|
10148
|
+
const absSkillPath = path18.join(absSkillsDir, SKILL_NAME);
|
|
10092
10149
|
const type = opts.copy ? "copy" : "symlink";
|
|
10093
10150
|
if (isOwnedSymlink(absSkillPath, source) || isOwnedCopy(absSkillPath)) {
|
|
10094
10151
|
try {
|
|
10095
10152
|
const lst = fs16.lstatSync(absSkillPath);
|
|
10096
10153
|
if (lst.isSymbolicLink()) {
|
|
10097
|
-
const dest =
|
|
10098
|
-
if (dest !==
|
|
10154
|
+
const dest = path18.resolve(path18.dirname(absSkillPath), fs16.readlinkSync(absSkillPath));
|
|
10155
|
+
if (dest !== path18.resolve(source)) {
|
|
10099
10156
|
if (opts.dryRun) {
|
|
10100
10157
|
results.push({ tool: id, path: absSkillPath, type: "symlink", status: "planned", message: "would re-point stale symlink to current source" });
|
|
10101
10158
|
continue;
|
|
@@ -10171,7 +10228,7 @@ function uninstallSkill(toolIds, opts = {}) {
|
|
|
10171
10228
|
}
|
|
10172
10229
|
}
|
|
10173
10230
|
for (const { tool, path: skillPath } of targets.values()) {
|
|
10174
|
-
if (toolIds
|
|
10231
|
+
if (toolIds !== void 0 && !toolIds.includes(tool)) {
|
|
10175
10232
|
const e = manifest.entries.find((x) => x.path === skillPath);
|
|
10176
10233
|
if (e) keep.push(e);
|
|
10177
10234
|
continue;
|
|
@@ -10232,19 +10289,19 @@ function reconcileSkillInstalls(opts = {}) {
|
|
|
10232
10289
|
if (e.type !== "symlink") {
|
|
10233
10290
|
const stale2 = currentVersion !== null && e.version !== currentVersion;
|
|
10234
10291
|
if (stale2 && source !== null && fs16.existsSync(e.path) && isOwnedCopy(e.path)) {
|
|
10235
|
-
const dir2 =
|
|
10236
|
-
const base2 =
|
|
10292
|
+
const dir2 = path18.dirname(e.path);
|
|
10293
|
+
const base2 = path18.basename(e.path);
|
|
10237
10294
|
try {
|
|
10238
10295
|
for (const name of fs16.readdirSync(dir2)) {
|
|
10239
10296
|
if (name.startsWith(`.${base2}.new-`) || name.startsWith(`.${base2}.old-`)) {
|
|
10240
|
-
fs16.rmSync(
|
|
10297
|
+
fs16.rmSync(path18.join(dir2, name), { recursive: true, force: true });
|
|
10241
10298
|
}
|
|
10242
10299
|
}
|
|
10243
10300
|
} catch {
|
|
10244
10301
|
}
|
|
10245
10302
|
const suffix = crypto10.randomBytes(4).toString("hex");
|
|
10246
|
-
const fresh =
|
|
10247
|
-
const retired =
|
|
10303
|
+
const fresh = path18.join(dir2, `.${base2}.new-${suffix}`);
|
|
10304
|
+
const retired = path18.join(dir2, `.${base2}.old-${suffix}`);
|
|
10248
10305
|
try {
|
|
10249
10306
|
copyDir(source, fresh);
|
|
10250
10307
|
} catch {
|
|
@@ -10290,12 +10347,12 @@ function reconcileSkillInstalls(opts = {}) {
|
|
|
10290
10347
|
}
|
|
10291
10348
|
continue;
|
|
10292
10349
|
}
|
|
10293
|
-
const dir =
|
|
10294
|
-
const base =
|
|
10350
|
+
const dir = path18.dirname(e.path);
|
|
10351
|
+
const base = path18.basename(e.path);
|
|
10295
10352
|
try {
|
|
10296
10353
|
for (const name of fs16.readdirSync(dir)) {
|
|
10297
10354
|
if (name.startsWith(`.${base}.new-`)) {
|
|
10298
|
-
fs16.rmSync(
|
|
10355
|
+
fs16.rmSync(path18.join(dir, name), { recursive: true, force: true });
|
|
10299
10356
|
}
|
|
10300
10357
|
}
|
|
10301
10358
|
} catch {
|
|
@@ -10311,10 +10368,10 @@ function reconcileSkillInstalls(opts = {}) {
|
|
|
10311
10368
|
const targetLive = fs16.existsSync(e.path);
|
|
10312
10369
|
let dest = null;
|
|
10313
10370
|
try {
|
|
10314
|
-
dest =
|
|
10371
|
+
dest = path18.resolve(path18.dirname(e.path), fs16.readlinkSync(e.path));
|
|
10315
10372
|
} catch {
|
|
10316
10373
|
}
|
|
10317
|
-
const stale = source !== null && dest !== null && dest !==
|
|
10374
|
+
const stale = source !== null && dest !== null && dest !== path18.resolve(source);
|
|
10318
10375
|
if (!targetLive || stale) {
|
|
10319
10376
|
if (source !== null) {
|
|
10320
10377
|
try {
|
|
@@ -10329,7 +10386,7 @@ function reconcileSkillInstalls(opts = {}) {
|
|
|
10329
10386
|
result.repointed.push(e.path);
|
|
10330
10387
|
} catch {
|
|
10331
10388
|
const suffix = crypto10.randomBytes(4).toString("hex");
|
|
10332
|
-
const fresh =
|
|
10389
|
+
const fresh = path18.join(dir, `.${base}.new-${suffix}`);
|
|
10333
10390
|
try {
|
|
10334
10391
|
copyDir(source, fresh);
|
|
10335
10392
|
fs16.renameSync(fresh, e.path);
|
|
@@ -10371,7 +10428,7 @@ function copyDir(src, dest) {
|
|
|
10371
10428
|
if (st.isDirectory()) {
|
|
10372
10429
|
fs16.mkdirSync(dest, { recursive: true });
|
|
10373
10430
|
for (const name of fs16.readdirSync(src)) {
|
|
10374
|
-
copyDir(
|
|
10431
|
+
copyDir(path18.join(src, name), path18.join(dest, name));
|
|
10375
10432
|
}
|
|
10376
10433
|
} else {
|
|
10377
10434
|
fs16.copyFileSync(src, dest);
|
|
@@ -10382,12 +10439,12 @@ var init_skill_installer = __esm({
|
|
|
10382
10439
|
"src/skill-install/skill-installer.ts"() {
|
|
10383
10440
|
"use strict";
|
|
10384
10441
|
HARNESSES = [
|
|
10385
|
-
{ id: "claude", label: "Claude", baseDir: ".claude", skillsDir:
|
|
10386
|
-
{ id: "openclaw", label: "OpenClaw", baseDir: ".openclaw", skillsDir:
|
|
10387
|
-
{ id: "pi", label: "pi", baseDir: ".pi", skillsDir:
|
|
10388
|
-
{ id: "cursor", label: "Cursor", baseDir: ".cursor", skillsDir:
|
|
10389
|
-
{ id: "codex", label: "OpenAI Codex CLI", baseDir: ".codex", skillsDir:
|
|
10390
|
-
{ id: "agents", label: "Cross-tool (~/.agents/skills)", baseDir: ".agents", skillsDir:
|
|
10442
|
+
{ id: "claude", label: "Claude", baseDir: ".claude", skillsDir: path18.join(".claude", "skills"), confidence: "confirmed" },
|
|
10443
|
+
{ id: "openclaw", label: "OpenClaw", baseDir: ".openclaw", skillsDir: path18.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)." },
|
|
10444
|
+
{ id: "pi", label: "pi", baseDir: ".pi", skillsDir: path18.join(".pi", "skills"), confidence: "confirmed" },
|
|
10445
|
+
{ id: "cursor", label: "Cursor", baseDir: ".cursor", skillsDir: path18.join(".cursor", "skills"), confidence: "partial", note: "Cursor project-level skills are documented; the global ~/.cursor/skills path is community-reported." },
|
|
10446
|
+
{ id: "codex", label: "OpenAI Codex CLI", baseDir: ".codex", skillsDir: path18.join(".codex", "skills"), confidence: "unverified", note: "Codex skills support is emerging; path not confirmed by official docs." },
|
|
10447
|
+
{ id: "agents", label: "Cross-tool (~/.agents/skills)", baseDir: ".agents", skillsDir: path18.join(".agents", "skills"), confidence: "unverified", note: "Proposed Universal Agents convention; not yet a ratified standard." }
|
|
10391
10448
|
];
|
|
10392
10449
|
SKILL_NAME = "pi-research";
|
|
10393
10450
|
PACKAGE_NAME = "@lincoln504/pi-research";
|
|
@@ -10400,7 +10457,7 @@ var init_skill_installer = __esm({
|
|
|
10400
10457
|
// src/cli.ts
|
|
10401
10458
|
import * as fs17 from "node:fs";
|
|
10402
10459
|
import * as os7 from "node:os";
|
|
10403
|
-
import * as
|
|
10460
|
+
import * as path19 from "node:path";
|
|
10404
10461
|
import { fileURLToPath as fileURLToPath5 } from "node:url";
|
|
10405
10462
|
|
|
10406
10463
|
// src/core/service-initialization.ts
|
|
@@ -10814,7 +10871,7 @@ function delimiterRegex(word) {
|
|
|
10814
10871
|
return new RegExp(`(^|\\n)([ \\t]*(?:#{1,6}[ \\t]*|>[ \\t]*|\\*\\*[ \\t]*)?)${word}\\b`, "i");
|
|
10815
10872
|
}
|
|
10816
10873
|
function splitCoverageDigest(report) {
|
|
10817
|
-
if (!report) return { digest: "", body: report
|
|
10874
|
+
if (!report) return { digest: "", body: report };
|
|
10818
10875
|
const headerMatch = delimiterRegex(COVERAGE_DIGEST_HEADER).exec(report);
|
|
10819
10876
|
if (!headerMatch) return { digest: "", body: report };
|
|
10820
10877
|
const lineStart = headerMatch.index + headerMatch[1].length;
|
|
@@ -11540,14 +11597,24 @@ ${previousQueries.map((q) => `- ${q}`).join("\n")}
|
|
|
11540
11597
|
complexity,
|
|
11541
11598
|
observer
|
|
11542
11599
|
};
|
|
11543
|
-
const responseText = isRouter ? await
|
|
11544
|
-
...
|
|
11545
|
-
|
|
11546
|
-
|
|
11547
|
-
|
|
11548
|
-
|
|
11549
|
-
|
|
11550
|
-
|
|
11600
|
+
const responseText = isRouter ? await (() => {
|
|
11601
|
+
const routerMessage = buildRouterMessage(...this.buildRouterEvidence(options, round), runContext);
|
|
11602
|
+
const window = model.contextWindow ?? DEFAULT_MODEL_CONTEXT_WINDOW;
|
|
11603
|
+
const estimatedTokens = routerMessage.length / CHARS_PER_TOKEN + ROUTER_MAX_TOKENS + SYNTHESIS_OVERHEAD_TOKENS;
|
|
11604
|
+
if (estimatedTokens > window) {
|
|
11605
|
+
logger.warn(
|
|
11606
|
+
`[PlanningService] Router request for ${model.id} is an estimated ${Math.round(estimatedTokens)} tokens against a ${window}-token context window (evidence ${routerMessage.length} chars, output ceiling ${ROUTER_MAX_TOKENS} tokens). Unlike the synthesizer, the router has no reduce path for an over-budget round \u2014 sending it anyway, the provider may reject this request.`
|
|
11607
|
+
);
|
|
11608
|
+
}
|
|
11609
|
+
return this.callLead({
|
|
11610
|
+
...callOptions,
|
|
11611
|
+
systemPrompt: populate(""),
|
|
11612
|
+
userMessage: routerMessage,
|
|
11613
|
+
maxTokens: ROUTER_MAX_TOKENS,
|
|
11614
|
+
component: "router",
|
|
11615
|
+
label: "router-updatePlanForRound"
|
|
11616
|
+
});
|
|
11617
|
+
})() : await this.synthesizeCorpus({ ...callOptions, reports, runContext, populate });
|
|
11551
11618
|
let plan = null;
|
|
11552
11619
|
try {
|
|
11553
11620
|
plan = this.parseJsonPlan(responseText);
|
|
@@ -11795,6 +11862,13 @@ ${previousQueries.map((q) => `- ${q}`).join("\n")}
|
|
|
11795
11862
|
`[PlanningService] Synthesis corpus is still ${finalSize} chars after ${MAX_SYNTHESIS_REDUCE_PASSES} reduce pass(es), over the ${budgetChars}-char budget for ${args.model.id}. Sending it anyway \u2014 the provider may reject this request.`
|
|
11796
11863
|
);
|
|
11797
11864
|
}
|
|
11865
|
+
const window = args.model.contextWindow ?? DEFAULT_MODEL_CONTEXT_WINDOW;
|
|
11866
|
+
const estimatedTotalTokens = (finalSize + globalSourceList.length + runContext.length) / CHARS_PER_TOKEN + config.SYNTHESIS_MAX_TOKENS + SYNTHESIS_OVERHEAD_TOKENS;
|
|
11867
|
+
if (estimatedTotalTokens > window) {
|
|
11868
|
+
logger.warn(
|
|
11869
|
+
`[PlanningService] Synthesis request for ${args.model.id} is an estimated ${Math.round(estimatedTotalTokens)} tokens against a ${window}-token context window (corpus ${finalSize} chars, source list ${globalSourceList.length} chars, run context ${runContext.length} chars, output ceiling ${config.SYNTHESIS_MAX_TOKENS} tokens) \u2014 the source list and/or run context are large enough on their own to overflow the window even though the reduced corpus fits its budget. Sending it anyway \u2014 the provider may reject this request.`
|
|
11870
|
+
);
|
|
11871
|
+
}
|
|
11798
11872
|
return this.callLead({
|
|
11799
11873
|
...args,
|
|
11800
11874
|
systemPrompt: populate(reduced ? MERGE_SYNTHESIS_SECTION : ""),
|
|
@@ -12175,10 +12249,13 @@ function isFullMockMode() {
|
|
|
12175
12249
|
}
|
|
12176
12250
|
var HEALTHCHECK_MIN_BUDGET_MS = 45e3 + 6e4;
|
|
12177
12251
|
var HEALTHCHECK_NAV_ATTEMPTS = 3;
|
|
12252
|
+
var BROWSER_LAUNCH_TIMEOUT_MS = 9e4;
|
|
12253
|
+
var CONTEXT_CREATION_TIMEOUT_MS = 3e4;
|
|
12254
|
+
var COLD_START_ALLOWANCE_MS = BROWSER_LAUNCH_TIMEOUT_MS + CONTEXT_CREATION_TIMEOUT_MS;
|
|
12178
12255
|
function getHealthCheckBudgetMs(config) {
|
|
12179
12256
|
const c = config || getConfig();
|
|
12180
12257
|
const ms = (value, fallback) => typeof value === "number" && Number.isFinite(value) ? value : fallback;
|
|
12181
|
-
const longestSlotHold = Math.max(ms(c.SEARCH_TIMEOUT_MS, 45e3), ms(c.SCRAPE_TIMEOUT_MS, 15e3)) + ms(c.BROWSER_TASK_TIMEOUT_MS, 1e4);
|
|
12258
|
+
const longestSlotHold = Math.max(ms(c.SEARCH_TIMEOUT_MS, 45e3), ms(c.SCRAPE_TIMEOUT_MS, 15e3)) + ms(c.BROWSER_TASK_TIMEOUT_MS, 1e4) + COLD_START_ALLOWANCE_MS;
|
|
12182
12259
|
const probeWork = HEALTHCHECK_NAV_ATTEMPTS * Math.max(1e4, ms(c.HEALTH_CHECK_TIMEOUT_MS, 1e4));
|
|
12183
12260
|
return Math.max(HEALTHCHECK_MIN_BUDGET_MS, longestSlotHold + probeWork);
|
|
12184
12261
|
}
|
|
@@ -12234,9 +12311,9 @@ var BrowserClient = class {
|
|
|
12234
12311
|
get targetPort() {
|
|
12235
12312
|
return this.port;
|
|
12236
12313
|
}
|
|
12237
|
-
async request(
|
|
12314
|
+
async request(path20, data, signal, config) {
|
|
12238
12315
|
const start = Date.now();
|
|
12239
|
-
const operation =
|
|
12316
|
+
const operation = path20.includes("/search") ? "search" : path20.includes("/scrape") ? "browser-task" : path20.includes("/healthcheck") ? "healthcheck" : "network";
|
|
12240
12317
|
return new Promise((resolve9, reject) => {
|
|
12241
12318
|
const agent = getClientAgent();
|
|
12242
12319
|
const timeoutMs = resolveClientRequestTimeoutMs(operation, config);
|
|
@@ -12261,7 +12338,7 @@ var BrowserClient = class {
|
|
|
12261
12338
|
resolved = true;
|
|
12262
12339
|
abortCleanup?.();
|
|
12263
12340
|
controller.abort();
|
|
12264
|
-
const error = new Error(`[BrowserClient] Request to ${
|
|
12341
|
+
const error = new Error(`[BrowserClient] Request to ${path20} timed out after ${timeoutMs}ms (Shared queue may be deep)`);
|
|
12265
12342
|
errorTracker.trackError(error, {
|
|
12266
12343
|
component: "browser-manager",
|
|
12267
12344
|
operation,
|
|
@@ -12274,7 +12351,7 @@ var BrowserClient = class {
|
|
|
12274
12351
|
const req = http4.request({
|
|
12275
12352
|
hostname: "127.0.0.1",
|
|
12276
12353
|
port: this.port,
|
|
12277
|
-
path:
|
|
12354
|
+
path: path20,
|
|
12278
12355
|
method: "POST",
|
|
12279
12356
|
agent,
|
|
12280
12357
|
signal: controller.signal,
|
|
@@ -12298,7 +12375,7 @@ var BrowserClient = class {
|
|
|
12298
12375
|
const error = new Error(
|
|
12299
12376
|
`Browser pool leader is draining (${parsed.reason || "leadership-lost"}) \u2014 re-elect and retry`
|
|
12300
12377
|
);
|
|
12301
|
-
logger.debug(`[BrowserClient] Leader draining on ${
|
|
12378
|
+
logger.debug(`[BrowserClient] Leader draining on ${path20}; re-election required.`);
|
|
12302
12379
|
errorTracker.trackError(error, {
|
|
12303
12380
|
component: "browser-manager",
|
|
12304
12381
|
operation,
|
|
@@ -12314,7 +12391,7 @@ var BrowserClient = class {
|
|
|
12314
12391
|
});
|
|
12315
12392
|
reject(error);
|
|
12316
12393
|
} else {
|
|
12317
|
-
logger.debug(`[BrowserClient] Request ${
|
|
12394
|
+
logger.debug(`[BrowserClient] Request ${path20} completed in ${duration}ms`);
|
|
12318
12395
|
resolve9(parsed);
|
|
12319
12396
|
}
|
|
12320
12397
|
} catch (_e) {
|
|
@@ -12334,7 +12411,7 @@ var BrowserClient = class {
|
|
|
12334
12411
|
resolved = true;
|
|
12335
12412
|
clearTimeout(timer);
|
|
12336
12413
|
abortCleanup?.();
|
|
12337
|
-
const error = new Error(`[BrowserClient] Response stream error on ${
|
|
12414
|
+
const error = new Error(`[BrowserClient] Response stream error on ${path20}: ${err.message}`);
|
|
12338
12415
|
errorTracker.trackError(error, {
|
|
12339
12416
|
component: "browser-manager",
|
|
12340
12417
|
operation,
|
|
@@ -12353,20 +12430,20 @@ var BrowserClient = class {
|
|
|
12353
12430
|
let errorMsg;
|
|
12354
12431
|
let errorType;
|
|
12355
12432
|
if (nodeErr.code === "ECONNRESET" || nodeErr.code === "EPIPE") {
|
|
12356
|
-
errorMsg = `Browser pool socket ${
|
|
12433
|
+
errorMsg = `Browser pool socket ${path20} closed (pool likely busy or restarting) - ${err.message}`;
|
|
12357
12434
|
errorType = "connection_reset";
|
|
12358
12435
|
} else if (nodeErr.code === "ECONNREFUSED") {
|
|
12359
|
-
errorMsg = `Browser pool ${
|
|
12436
|
+
errorMsg = `Browser pool ${path20} unreachable (server restarting or torn down) - ${err.message}`;
|
|
12360
12437
|
errorType = "connection_refused";
|
|
12361
12438
|
} else if (nodeErr.code === "ETIMEDOUT") {
|
|
12362
|
-
errorMsg = `Browser pool ${
|
|
12439
|
+
errorMsg = `Browser pool ${path20} timed out (slow browser response) - ${err.message}`;
|
|
12363
12440
|
errorType = "timeout";
|
|
12364
12441
|
} else {
|
|
12365
|
-
errorMsg = `Browser pool ${
|
|
12442
|
+
errorMsg = `Browser pool ${path20} error: ${err.message}`;
|
|
12366
12443
|
errorType = "unknown";
|
|
12367
12444
|
}
|
|
12368
12445
|
const error = new Error(errorMsg);
|
|
12369
|
-
logger.error(`[BrowserClient] Request to http://127.0.0.1:${this.port}${
|
|
12446
|
+
logger.error(`[BrowserClient] Request to http://127.0.0.1:${this.port}${path20} failed:`, errorMsg);
|
|
12370
12447
|
errorTracker.trackError(error, {
|
|
12371
12448
|
component: "browser-manager",
|
|
12372
12449
|
operation,
|
|
@@ -13246,7 +13323,7 @@ var BrowserTaskScheduler = class {
|
|
|
13246
13323
|
const pool = await (await this.getWorkerPoolManager()).ensurePool(config);
|
|
13247
13324
|
const startTime = Date.now();
|
|
13248
13325
|
const cfg = config || getConfig();
|
|
13249
|
-
const timeoutMs = cfg.SEARCH_TIMEOUT_MS + cfg.BROWSER_TASK_TIMEOUT_MS;
|
|
13326
|
+
const timeoutMs = cfg.SEARCH_TIMEOUT_MS + cfg.BROWSER_TASK_TIMEOUT_MS + COLD_START_ALLOWANCE_MS;
|
|
13250
13327
|
let timeoutId;
|
|
13251
13328
|
let dispatchedAt = 0;
|
|
13252
13329
|
let startDeadline = () => {
|
|
@@ -13335,7 +13412,7 @@ var BrowserTaskScheduler = class {
|
|
|
13335
13412
|
const startTime = Date.now();
|
|
13336
13413
|
const cfg = config || getConfig();
|
|
13337
13414
|
const isMocking = process.env["PI_RESEARCH_MOCK_SCRAPE"] === "true";
|
|
13338
|
-
const timeoutMs = cfg.SCRAPE_TIMEOUT_MS + (isMocking ? 5e3 : cfg.BROWSER_TASK_TIMEOUT_MS);
|
|
13415
|
+
const timeoutMs = cfg.SCRAPE_TIMEOUT_MS + (isMocking ? 5e3 : cfg.BROWSER_TASK_TIMEOUT_MS + COLD_START_ALLOWANCE_MS);
|
|
13339
13416
|
let timeoutId;
|
|
13340
13417
|
let dispatchedAt = 0;
|
|
13341
13418
|
let startDeadline = () => {
|
|
@@ -15393,8 +15470,8 @@ var StateValidator = class {
|
|
|
15393
15470
|
if (!Value4.Check(SingletonStateSchema, coerced)) {
|
|
15394
15471
|
const errors = [...Value4.Errors(SingletonStateSchema, coerced)];
|
|
15395
15472
|
const errorMsg = errors.map((e) => {
|
|
15396
|
-
const
|
|
15397
|
-
return `${
|
|
15473
|
+
const path20 = String(e.path || "root");
|
|
15474
|
+
return `${path20}: ${e.message} (${JSON.stringify(e.value)})`;
|
|
15398
15475
|
}).join(", ");
|
|
15399
15476
|
throw new Error(`Invalid state: ${errorMsg}`);
|
|
15400
15477
|
}
|
|
@@ -17123,7 +17200,7 @@ async function performSearch(queries, config, signal, onProgress, container = ge
|
|
|
17123
17200
|
metrics.increment("browser_search_orchestrations_total", 1);
|
|
17124
17201
|
logger.log(`[Search] Orchestrating ${queries.length} queries across ${maxWorkers} worker processes...`);
|
|
17125
17202
|
if (onProgress) onProgress(0);
|
|
17126
|
-
const QUERY_TIMEOUT_MS = (config?.SEARCH_TIMEOUT_MS ?? 45e3) + (config?.BROWSER_TASK_TIMEOUT_MS ?? 1e4);
|
|
17203
|
+
const QUERY_TIMEOUT_MS = (config?.SEARCH_TIMEOUT_MS ?? 45e3) + (config?.BROWSER_TASK_TIMEOUT_MS ?? 1e4) + COLD_START_ALLOWANCE_MS;
|
|
17127
17204
|
const filteredQueries = [...new Set(queries.filter((q) => q.trim()))];
|
|
17128
17205
|
let timeoutCount = 0;
|
|
17129
17206
|
let errorCount = 0;
|
|
@@ -18158,28 +18235,31 @@ async function extractPdfToMarkdown(bytes) {
|
|
|
18158
18235
|
}
|
|
18159
18236
|
try {
|
|
18160
18237
|
const doc = new WasmPdfDocument(bytes);
|
|
18161
|
-
|
|
18162
|
-
|
|
18238
|
+
try {
|
|
18239
|
+
const pageCount = doc.pageCount();
|
|
18240
|
+
let markdown = `# PDF Document
|
|
18163
18241
|
|
|
18164
18242
|
**Pages:** ${pageCount}
|
|
18165
18243
|
|
|
18166
18244
|
`;
|
|
18167
|
-
|
|
18168
|
-
|
|
18169
|
-
|
|
18170
|
-
|
|
18171
|
-
|
|
18245
|
+
try {
|
|
18246
|
+
markdown += doc.toMarkdownAll();
|
|
18247
|
+
} catch {
|
|
18248
|
+
for (let i = 0; i < pageCount; i++) {
|
|
18249
|
+
markdown += `## Page ${i + 1}
|
|
18172
18250
|
|
|
18173
18251
|
${doc.toMarkdown(i)}
|
|
18174
18252
|
|
|
18175
18253
|
`;
|
|
18254
|
+
}
|
|
18176
18255
|
}
|
|
18256
|
+
const pdfExtractionDuration = Date.now() - pdfExtractionStart;
|
|
18257
|
+
metrics.observe("scrape_pdf_conversion_ms", pdfExtractionDuration);
|
|
18258
|
+
metrics.increment("scrape_pdf_conversions_total", 1, { status: "success", pages: String(pageCount) });
|
|
18259
|
+
return markdown;
|
|
18260
|
+
} finally {
|
|
18261
|
+
doc.free();
|
|
18177
18262
|
}
|
|
18178
|
-
doc.free();
|
|
18179
|
-
const pdfExtractionDuration = Date.now() - pdfExtractionStart;
|
|
18180
|
-
metrics.observe("scrape_pdf_conversion_ms", pdfExtractionDuration);
|
|
18181
|
-
metrics.increment("scrape_pdf_conversions_total", 1, { status: "success", pages: String(pageCount) });
|
|
18182
|
-
return markdown;
|
|
18183
18263
|
} catch (e) {
|
|
18184
18264
|
const msg2 = e instanceof Error ? e.message : String(e);
|
|
18185
18265
|
logger.debug(`[Scrapers] PDF extraction failed: ${msg2}`);
|
|
@@ -18279,7 +18359,7 @@ async function scrapeWithFetch(url, signal) {
|
|
|
18279
18359
|
const contentLength = response.headers.get("content-length");
|
|
18280
18360
|
if (contentLength) {
|
|
18281
18361
|
const size = parseInt(contentLength, 10);
|
|
18282
|
-
if (contentType.includes("application/pdf") ||
|
|
18362
|
+
if (contentType.includes("application/pdf") || currentUrl.toLowerCase().endsWith(".pdf")) {
|
|
18283
18363
|
if (size > MAX_PDF_SIZE) {
|
|
18284
18364
|
const sizeMB = Math.round(size / 1024 / 1024);
|
|
18285
18365
|
logger.warn(`[Scrapers] PDF too large (Content-Length: ${sizeMB}MB, max 100MB), skipping`);
|
|
@@ -18301,7 +18381,7 @@ async function scrapeWithFetch(url, signal) {
|
|
|
18301
18381
|
throw new Error(`HTML response too large (${sizeMB}MB, max 25MB)`);
|
|
18302
18382
|
}
|
|
18303
18383
|
}
|
|
18304
|
-
if (contentType.includes("application/pdf") ||
|
|
18384
|
+
if (contentType.includes("application/pdf") || currentUrl.toLowerCase().endsWith(".pdf")) {
|
|
18305
18385
|
let pdfBytes;
|
|
18306
18386
|
try {
|
|
18307
18387
|
pdfBytes = await readBodyCapped(response, MAX_PDF_SIZE);
|
|
@@ -20426,14 +20506,6 @@ function createSecuritySearchTool(options) {
|
|
|
20426
20506
|
details: { error: "invalid_parameters" }
|
|
20427
20507
|
};
|
|
20428
20508
|
}
|
|
20429
|
-
const allowed = options.tracker.recordCall("security_search");
|
|
20430
|
-
if (!allowed) {
|
|
20431
|
-
metrics.increment("tool_security_search_calls_total", 1, { status: "rate_limited" });
|
|
20432
|
-
return {
|
|
20433
|
-
content: [{ type: "text", text: options.tracker.getLimitMessage("security_search") }],
|
|
20434
|
-
details: { blocked: true, reason: "limit_reached" }
|
|
20435
|
-
};
|
|
20436
|
-
}
|
|
20437
20509
|
const p = params;
|
|
20438
20510
|
const terms = p.terms;
|
|
20439
20511
|
if (terms.length === 0) {
|
|
@@ -20475,6 +20547,14 @@ function createSecuritySearchTool(options) {
|
|
|
20475
20547
|
}
|
|
20476
20548
|
}
|
|
20477
20549
|
const maxResults = p.maxResults ?? 20;
|
|
20550
|
+
const allowed = options.tracker.recordCall("security_search");
|
|
20551
|
+
if (!allowed) {
|
|
20552
|
+
metrics.increment("tool_security_search_calls_total", 1, { status: "rate_limited" });
|
|
20553
|
+
return {
|
|
20554
|
+
content: [{ type: "text", text: options.tracker.getLimitMessage("security_search") }],
|
|
20555
|
+
details: { blocked: true, reason: "limit_reached" }
|
|
20556
|
+
};
|
|
20557
|
+
}
|
|
20478
20558
|
metrics.increment("tool_security_search_terms_total", terms.length);
|
|
20479
20559
|
metrics.increment("tool_security_search_databases_total", databases.length);
|
|
20480
20560
|
let results;
|
|
@@ -20882,20 +20962,19 @@ var StackExchangeClient = class {
|
|
|
20882
20962
|
}
|
|
20883
20963
|
});
|
|
20884
20964
|
}
|
|
20965
|
+
// Pure accessor — no metrics side effects. `stackexchangeCommand()` calls this
|
|
20966
|
+
// repeatedly per invocation (pre-request gate checks, success footer, error
|
|
20967
|
+
// footer) purely to read current state for display; incrementing counters here
|
|
20968
|
+
// used to record 2-3x the real number of quota-low/exhausted events per call.
|
|
20969
|
+
// The counters are incremented once, at the single gate check that actually
|
|
20970
|
+
// observes each event (see stackexchange/index.ts).
|
|
20885
20971
|
getQuotaInfo() {
|
|
20886
|
-
|
|
20972
|
+
return {
|
|
20887
20973
|
remaining: this.quotaRemaining,
|
|
20888
20974
|
max: this.quotaMax,
|
|
20889
20975
|
requestCount: this.requestCount,
|
|
20890
20976
|
lastBackoff: this.lastBackoff
|
|
20891
20977
|
};
|
|
20892
|
-
if (this.isQuotaExhausted()) {
|
|
20893
|
-
metrics.increment("stackexchange_quota_exhausted_total", 1);
|
|
20894
|
-
}
|
|
20895
|
-
if (this.isQuotaLow()) {
|
|
20896
|
-
metrics.increment("stackexchange_quota_low_total", 1);
|
|
20897
|
-
}
|
|
20898
|
-
return info;
|
|
20899
20978
|
}
|
|
20900
20979
|
isQuotaExhausted() {
|
|
20901
20980
|
return this.quotaExhaustedAt !== null && Date.now() - this.quotaExhaustedAt < QUOTA_PROBE_COOLDOWN_MS;
|
|
@@ -21174,6 +21253,7 @@ function formatSitesCompact(sites) {
|
|
|
21174
21253
|
|
|
21175
21254
|
// src/stackexchange/index.ts
|
|
21176
21255
|
init_logger();
|
|
21256
|
+
init_metrics();
|
|
21177
21257
|
function notify(ctx, message, type) {
|
|
21178
21258
|
ctx.ui?.notify?.(message, type);
|
|
21179
21259
|
}
|
|
@@ -21200,6 +21280,7 @@ async function stackexchangeCommand(options) {
|
|
|
21200
21280
|
const client = getClient(config);
|
|
21201
21281
|
try {
|
|
21202
21282
|
if (client.isQuotaExhausted()) {
|
|
21283
|
+
metrics.increment("stackexchange_quota_exhausted_total", 1);
|
|
21203
21284
|
const quota2 = client.getQuotaInfo();
|
|
21204
21285
|
notify(
|
|
21205
21286
|
ctx,
|
|
@@ -21215,6 +21296,7 @@ async function stackexchangeCommand(options) {
|
|
|
21215
21296
|
};
|
|
21216
21297
|
}
|
|
21217
21298
|
if (client.isQuotaLow()) {
|
|
21299
|
+
metrics.increment("stackexchange_quota_low_total", 1);
|
|
21218
21300
|
const quota2 = client.getQuotaInfo();
|
|
21219
21301
|
notify(
|
|
21220
21302
|
ctx,
|
|
@@ -21663,6 +21745,7 @@ init_http_body();
|
|
|
21663
21745
|
init_logger();
|
|
21664
21746
|
init_safe_unref();
|
|
21665
21747
|
init_metrics();
|
|
21748
|
+
init_http_body();
|
|
21666
21749
|
var DEFAULT_REQUEST_KEY = "O43z0dpjhgX20SCx4KAo";
|
|
21667
21750
|
var BRIDGED_GLOBALS = ["window", "document", "location", "navigator", "origin"];
|
|
21668
21751
|
function describeError(error) {
|
|
@@ -21762,7 +21845,10 @@ async function doMint(identifiers, opts) {
|
|
|
21762
21845
|
body: JSON.stringify([requestKey, botguardResponse]),
|
|
21763
21846
|
signal: opts.signal
|
|
21764
21847
|
});
|
|
21765
|
-
|
|
21848
|
+
if (!integrityResponse.ok) {
|
|
21849
|
+
throw new Error(`HTTP ${integrityResponse.status} from attestation server`);
|
|
21850
|
+
}
|
|
21851
|
+
const integrityJson = await readJsonCapped(integrityResponse);
|
|
21766
21852
|
const integrityToken = integrityJson?.[0];
|
|
21767
21853
|
if (!integrityToken) {
|
|
21768
21854
|
throw new Error("Integrity token rejected by attestation server (likely a non-residential IP)");
|
|
@@ -21817,7 +21903,16 @@ async function fetchVideoTranscripts(videoIds, opts = {}) {
|
|
|
21817
21903
|
if (log?.setLevel && log.Level) log.setLevel(log.Level.NONE);
|
|
21818
21904
|
} catch {
|
|
21819
21905
|
}
|
|
21820
|
-
const
|
|
21906
|
+
const ssrfFetcher = await getSsrfSafeFetcher();
|
|
21907
|
+
const fetchImpl = ssrfFetcher ? (input, init) => {
|
|
21908
|
+
if (typeof input === "string" || input instanceof URL) {
|
|
21909
|
+
return ssrfFetcher.fetch(String(input), {
|
|
21910
|
+
...init ?? {},
|
|
21911
|
+
dispatcher: ssrfFetcher.dispatcher
|
|
21912
|
+
});
|
|
21913
|
+
}
|
|
21914
|
+
return fetch(input, init);
|
|
21915
|
+
} : (input, init) => fetch(input, init);
|
|
21821
21916
|
let visitorData;
|
|
21822
21917
|
try {
|
|
21823
21918
|
const seed = await breaker.execute(() => withTimeout2(
|
|
@@ -21900,10 +21995,27 @@ async function fetchOne(session, videoId, contentToken, lang, timeoutMs, signal,
|
|
|
21900
21995
|
const sep3 = track.base_url.includes("?") ? "&" : "?";
|
|
21901
21996
|
const url = `${track.base_url}${sep3}fmt=json3&c=WEB&pot=${encodeURIComponent(contentToken)}`;
|
|
21902
21997
|
const fetchSignal = createTimeoutSignal(timeoutMs, signal);
|
|
21903
|
-
const
|
|
21904
|
-
|
|
21905
|
-
|
|
21906
|
-
|
|
21998
|
+
const MAX_REDIRECTS = 5;
|
|
21999
|
+
let currentUrl = url;
|
|
22000
|
+
let response;
|
|
22001
|
+
for (let hop = 0; hop <= MAX_REDIRECTS; hop++) {
|
|
22002
|
+
response = await fetchImpl(currentUrl, {
|
|
22003
|
+
signal: fetchSignal,
|
|
22004
|
+
redirect: "manual",
|
|
22005
|
+
headers: { "User-Agent": "Mozilla/5.0", "Accept-Language": lang }
|
|
22006
|
+
});
|
|
22007
|
+
if (response.status >= 300 && response.status < 400) {
|
|
22008
|
+
const location = response.headers.get("location");
|
|
22009
|
+
if (!location) break;
|
|
22010
|
+
const resolved = new URL(location, currentUrl).href;
|
|
22011
|
+
assertTrustedTimedtextUrl(resolved);
|
|
22012
|
+
void response.body?.cancel()?.catch(() => {
|
|
22013
|
+
});
|
|
22014
|
+
currentUrl = resolved;
|
|
22015
|
+
continue;
|
|
22016
|
+
}
|
|
22017
|
+
break;
|
|
22018
|
+
}
|
|
21907
22019
|
if (!response.ok) {
|
|
21908
22020
|
void response.body?.cancel()?.catch(() => {
|
|
21909
22021
|
});
|
|
@@ -23898,7 +24010,6 @@ var DeepResearchOrchestrator = class {
|
|
|
23898
24010
|
if (!routerChoseSynthesis) {
|
|
23899
24011
|
if (this.currentRound > lastAnnouncedRound) {
|
|
23900
24012
|
observer?.onRoundStart?.(this.currentRound);
|
|
23901
|
-
lastAnnouncedRound = this.currentRound;
|
|
23902
24013
|
}
|
|
23903
24014
|
observer?.onEvaluationStart?.(this.currentRound);
|
|
23904
24015
|
observer?.onEvaluationProgress?.("evaluating");
|
|
@@ -25303,6 +25414,10 @@ async function _doInit(options = {}) {
|
|
|
25303
25414
|
}
|
|
25304
25415
|
}
|
|
25305
25416
|
async function initResearchSDK(options = {}) {
|
|
25417
|
+
if (_shutdownPromise) {
|
|
25418
|
+
await _shutdownPromise.catch(() => {
|
|
25419
|
+
});
|
|
25420
|
+
}
|
|
25306
25421
|
if (isInitialized) {
|
|
25307
25422
|
logger.warn("[SDK] Research SDK already initialized. Call shutdownResearchSDK() first if you want to re-initialize with new options.");
|
|
25308
25423
|
return;
|
|
@@ -25653,7 +25768,7 @@ init_logger();
|
|
|
25653
25768
|
init_constants();
|
|
25654
25769
|
import { promises as fs15 } from "node:fs";
|
|
25655
25770
|
import { homedir as homedir4, tmpdir as tmpdir4, platform as platform3 } from "node:os";
|
|
25656
|
-
import { join as
|
|
25771
|
+
import { join as join20, resolve as resolve5, normalize, sep as sep2 } from "node:path";
|
|
25657
25772
|
import { randomBytes as randomBytes6 } from "node:crypto";
|
|
25658
25773
|
var PREFERRED_SUBDIRS = [
|
|
25659
25774
|
"research",
|
|
@@ -25724,7 +25839,7 @@ async function findPreferredSubdir(cwd) {
|
|
|
25724
25839
|
for (const target of PREFERRED_SUBDIRS) {
|
|
25725
25840
|
const match = entries.find((e) => e.toLowerCase() === target);
|
|
25726
25841
|
if (match === void 0) continue;
|
|
25727
|
-
const candidate =
|
|
25842
|
+
const candidate = join20(cwd, match);
|
|
25728
25843
|
try {
|
|
25729
25844
|
const stat6 = await fs15.stat(candidate);
|
|
25730
25845
|
if (stat6.isDirectory()) return candidate;
|
|
@@ -25769,7 +25884,7 @@ async function exportResearchReport(query, result, _mode, cwd, explicitDir) {
|
|
|
25769
25884
|
for (let attempt = 0; attempt < MAX_EXPORT_RETRIES; attempt++) {
|
|
25770
25885
|
const hash = generateHash();
|
|
25771
25886
|
const filename = `${baseFilename}-${hash}.md`;
|
|
25772
|
-
const filepath =
|
|
25887
|
+
const filepath = join20(targetDir, filename);
|
|
25773
25888
|
try {
|
|
25774
25889
|
await fs15.writeFile(filepath, result, { flag: "wx" });
|
|
25775
25890
|
logger.log(`[export] Research report saved to: ${filepath}`);
|
|
@@ -25966,8 +26081,8 @@ function resolvedConfigPaths() {
|
|
|
25966
26081
|
configDir,
|
|
25967
26082
|
cliIfaceEnv: getInterfaceEnvFilePath("cli"),
|
|
25968
26083
|
piIfaceEnv: getInterfaceEnvFilePath("pi"),
|
|
25969
|
-
piAuth:
|
|
25970
|
-
piModels:
|
|
26084
|
+
piAuth: path19.join(agentDir, "auth.json"),
|
|
26085
|
+
piModels: path19.join(agentDir, "models.json"),
|
|
25971
26086
|
piState: getStateDir()
|
|
25972
26087
|
};
|
|
25973
26088
|
}
|
|
@@ -26940,8 +27055,8 @@ Error: ${err.message}
|
|
|
26940
27055
|
var _isMain = (() => {
|
|
26941
27056
|
try {
|
|
26942
27057
|
if (!process.argv[1]) return false;
|
|
26943
|
-
const modulePath = fs17.realpathSync(
|
|
26944
|
-
const invokedPath = fs17.realpathSync(
|
|
27058
|
+
const modulePath = fs17.realpathSync(path19.resolve(fileURLToPath5(import.meta.url)));
|
|
27059
|
+
const invokedPath = fs17.realpathSync(path19.resolve(process.argv[1]));
|
|
26945
27060
|
return modulePath === invokedPath;
|
|
26946
27061
|
} catch {
|
|
26947
27062
|
return false;
|