pi-lens
Version:
Real-time code feedback for pi — LSP, linters, formatters, type-checking, structural analysis & booboo
1,251 lines (1,242 loc) • 40.4 kB
JavaScript
import { createRequire as __pilensCreateRequire } from "node:module"; const require = __pilensCreateRequire(import.meta.url);
import {
isUnderDir,
normalizeFilePath,
pathsEqual
} from "./chunk-Q5U6FMDI.js";
// dist/clients/env-utils.js
function isTestMode() {
if (process.env.PI_LENS_TEST_MODE === "1")
return true;
if (process.env.VITEST && process.env.PI_LENS_TEST_MODE !== "0")
return true;
return false;
}
function toPositiveFinite(value) {
const num = typeof value === "number" ? value : Number(value);
return Number.isFinite(num) && num > 0 ? num : 0;
}
function lazyEnvNumber(envName, fallback) {
let memo;
return {
get() {
if (memo !== void 0)
return memo;
const envValue = toPositiveFinite(process.env[envName]);
memo = envValue > 0 ? envValue : fallback;
return memo;
},
_resetForTests() {
memo = void 0;
}
};
}
// dist/clients/redact/secrets.js
var PEM_BEGIN = "-----BEGIN ";
var PEM_DELIMITER = "-----";
function codePointOf(char) {
return char?.codePointAt(0) ?? -1;
}
function isAsciiAlphaNumeric(char) {
const code = codePointOf(char);
return code >= 48 && code <= 57 || code >= 65 && code <= 90 || code >= 97 && code <= 122;
}
function isUpperAlphaNumeric(char) {
const code = codePointOf(char);
return code >= 48 && code <= 57 || code >= 65 && code <= 90;
}
function isIdentifierChar(char) {
return char === "_" || isAsciiAlphaNumeric(char);
}
function isTokenChar(char) {
return char === "-" || isIdentifierChar(char);
}
var PREFIXED_SECRETS = [
{
prefix: "ghp_",
minSuffixLength: 20,
predicate: isAsciiAlphaNumeric,
replacement: "[REDACTED:github-token]"
},
{
prefix: "gho_",
minSuffixLength: 20,
predicate: isAsciiAlphaNumeric,
replacement: "[REDACTED:github-token]"
},
{
prefix: "ghu_",
minSuffixLength: 20,
predicate: isAsciiAlphaNumeric,
replacement: "[REDACTED:github-token]"
},
{
prefix: "ghs_",
minSuffixLength: 20,
predicate: isAsciiAlphaNumeric,
replacement: "[REDACTED:github-token]"
},
{
prefix: "ghr_",
minSuffixLength: 20,
predicate: isAsciiAlphaNumeric,
replacement: "[REDACTED:github-token]"
},
{
prefix: "github_pat_",
minSuffixLength: 20,
predicate: isTokenChar,
replacement: "[REDACTED:github-token]"
},
{
prefix: "AKIA",
minSuffixLength: 16,
predicate: isUpperAlphaNumeric,
replacement: "[REDACTED:aws-access-key]"
},
{
prefix: "ASIA",
minSuffixLength: 16,
predicate: isUpperAlphaNumeric,
replacement: "[REDACTED:aws-access-key]"
},
{
prefix: "sk_live_",
minSuffixLength: 16,
predicate: isAsciiAlphaNumeric,
replacement: "[REDACTED:stripe-key]"
},
{
prefix: "sk_test_",
minSuffixLength: 16,
predicate: isAsciiAlphaNumeric,
replacement: "[REDACTED:stripe-key]"
},
{
prefix: "rk_live_",
minSuffixLength: 16,
predicate: isAsciiAlphaNumeric,
replacement: "[REDACTED:stripe-key]"
},
{
prefix: "rk_test_",
minSuffixLength: 16,
predicate: isAsciiAlphaNumeric,
replacement: "[REDACTED:stripe-key]"
},
{
prefix: "glpat-",
minSuffixLength: 20,
predicate: isTokenChar,
replacement: "[REDACTED:gitlab-token]"
},
{
prefix: "AIza",
minSuffixLength: 35,
predicate: isTokenChar,
replacement: "[REDACTED:google-api-key]"
},
{
prefix: "GOCSPX-",
minSuffixLength: 20,
predicate: isTokenChar,
replacement: "[REDACTED:google-oauth-secret]"
},
{
prefix: "sk-proj-",
minSuffixLength: 20,
predicate: isTokenChar,
replacement: "[REDACTED:openai-key]"
},
{
prefix: "sk-svcacct-",
minSuffixLength: 20,
predicate: isTokenChar,
replacement: "[REDACTED:openai-key]"
}
];
var OPENAI_LEGACY_MIN_SUFFIX = 40;
function consumeWhile(options) {
let end = options.start;
while (end < options.text.length && options.predicate(options.text[end])) {
end++;
}
return end;
}
function scanPrefixedSecret(position) {
for (const secret of PREFIXED_SECRETS) {
if (position.text[position.start] !== secret.prefix[0] || !position.text.startsWith(secret.prefix, position.start)) {
continue;
}
const suffixStart = position.start + secret.prefix.length;
const end = consumeWhile({
text: position.text,
start: suffixStart,
predicate: secret.predicate
});
return end - suffixStart >= secret.minSuffixLength ? { end, replacement: secret.replacement } : { end: Math.max(end, suffixStart) };
}
return void 0;
}
function scanSlackToken(position) {
const variant = position.text[position.start + 3];
if (!position.text.startsWith("xox", position.start) || variant === void 0 || variant < "a" || variant > "z" || position.text[position.start + 4] !== "-") {
return void 0;
}
const suffixStart = position.start + 5;
const end = consumeWhile({
text: position.text,
start: suffixStart,
predicate: isTokenChar
});
return end - suffixStart >= 10 ? { end, replacement: "[REDACTED:slack-token]" } : { end: Math.max(end, suffixStart) };
}
function scanSendGridKey(position) {
if (!position.text.startsWith("SG.", position.start))
return void 0;
const retryAt = position.start + 1;
const firstStart = position.start + 3;
const firstEnd = firstStart + 22;
for (let index = firstStart; index < firstEnd; index++) {
if (!isTokenChar(position.text[index]))
return { end: retryAt };
}
if (position.text[firstEnd] !== ".")
return { end: retryAt };
const secondStart = firstEnd + 1;
const end = secondStart + 43;
for (let index = secondStart; index < end; index++) {
if (!isTokenChar(position.text[index]))
return { end: retryAt };
}
return isTokenChar(position.text[end]) ? { end: retryAt } : { end, replacement: "[REDACTED:sendgrid-key]" };
}
function createJwtSegment(options) {
const possibleCandidate = options.text.indexOf("eyJ", options.start);
const candidate = possibleCandidate !== -1 && possibleCandidate < options.end && options.end - (possibleCandidate + 3) >= 5 ? possibleCandidate : -1;
return {
candidate,
length: options.end - options.start,
end: options.end
};
}
function chooseSecretRange(options) {
if (!options.candidate)
return options.current;
if (!options.current)
return options.candidate;
return options.candidate.start < options.current.start || options.candidate.start === options.current.start && options.candidate.end > options.current.end ? options.candidate : options.current;
}
function findNestedJwtRange(segments) {
let result;
const jweStart = segments.length - 5;
if (jweStart >= 0 && segments[jweStart].candidate !== -1 && segments[jweStart + 2].length > 0 && segments[jweStart + 3].length > 0 && segments[jweStart + 4].length > 0) {
result = {
start: segments[jweStart].candidate,
end: segments[jweStart + 4].end
};
}
const jwtStart = segments.length - 3;
const isJwt = jwtStart >= 0 && segments[jwtStart].candidate !== -1 && segments[jwtStart + 1].length > 0 && segments[jwtStart + 2].length > 0;
return isJwt ? chooseSecretRange({
current: result,
candidate: {
start: segments[jwtStart].candidate,
end: segments[jwtStart + 2].end
}
}) : result;
}
function topLevelJwtEnd(options) {
if (options.segmentCount >= 5 && options.segmentLengths[2] > 0 && options.segmentLengths[3] > 0 && options.segmentLengths[4] > 0) {
return options.segmentEnds[4];
}
return options.segmentCount >= 3 && options.segmentLengths[1] > 0 && options.segmentLengths[2] > 0 ? options.segmentEnds[2] : void 0;
}
function scanJwt(position) {
if (!position.text.startsWith("eyJ", position.start))
return void 0;
let segmentStart = position.start + 3;
let cursor = consumeWhile({
text: position.text,
start: segmentStart,
predicate: isTokenChar
});
if (cursor - segmentStart < 5)
return { end: cursor };
const segmentLengths = [cursor - segmentStart];
const segmentEnds = [cursor];
const recentSegments = [
createJwtSegment({ text: position.text, start: segmentStart, end: cursor })
];
let segmentCount = 1;
let nestedRange;
while (position.text[cursor] === ".") {
segmentStart = cursor + 1;
cursor = consumeWhile({
text: position.text,
start: segmentStart,
predicate: isTokenChar
});
segmentCount++;
recentSegments.push(createJwtSegment({
text: position.text,
start: segmentStart,
end: cursor
}));
if (recentSegments.length > 5)
recentSegments.shift();
nestedRange = chooseSecretRange({
current: nestedRange,
candidate: findNestedJwtRange(recentSegments)
});
if (segmentLengths.length < 5) {
segmentLengths.push(cursor - segmentStart);
segmentEnds.push(cursor);
}
}
const topLevelEnd = topLevelJwtEnd({
segmentCount,
segmentLengths,
segmentEnds
});
if (topLevelEnd !== void 0) {
return { end: topLevelEnd, replacement: "[REDACTED:jwt]" };
}
return nestedRange ? { ...nestedRange, replacement: "[REDACTED:jwt]" } : { end: cursor };
}
function scanOpenAiLegacyKey(position) {
if (!position.text.startsWith("sk-", position.start))
return void 0;
const suffixStart = position.start + 3;
let end = suffixStart;
let hasDigit = false;
while (end < position.text.length && isAsciiAlphaNumeric(position.text[end])) {
const code = codePointOf(position.text[end]);
if (code >= 48 && code <= 57)
hasDigit = true;
end++;
}
return end - suffixStart >= OPENAI_LEGACY_MIN_SUFFIX && hasDigit ? { end, replacement: "[REDACTED:openai-key]" } : { end: Math.max(end, suffixStart) };
}
function scanSecretAt(position) {
const initial = position.text[position.start];
if (initial === "S")
return scanSendGridKey(position);
if (initial === "x")
return scanSlackToken(position);
if (initial === "s") {
return scanPrefixedSecret(position) ?? scanOpenAiLegacyKey(position);
}
if (initial === "A" || initial === "G" || initial === "g" || initial === "r") {
return scanPrefixedSecret(position);
}
return void 0;
}
function redactScannedSecrets(options) {
let cursor = 0;
let copyFrom = 0;
let result = "";
const position = { text: options.text, start: 0 };
while (cursor < options.text.length) {
position.start = cursor;
const scan = options.scanner(position);
if (!scan) {
cursor++;
continue;
}
if (scan.replacement) {
const replacementStart = scan.start ?? cursor;
result += options.text.slice(copyFrom, replacementStart) + scan.replacement;
copyFrom = scan.end;
}
cursor = Math.max(cursor + 1, scan.end);
}
return copyFrom === 0 ? options.text : result + options.text.slice(copyFrom);
}
function redactTokenSecrets(text) {
return redactScannedSecrets({ text, scanner: scanSecretAt });
}
function redactJwtSecrets(text) {
return redactScannedSecrets({ text, scanner: scanJwt });
}
function isPrivateKeyLabel(label) {
if (label.length === 0 || label.length > 64 || !label.includes("PRIVATE KEY")) {
return false;
}
for (const char of label) {
const code = codePointOf(char);
if (char !== " " && (code < 48 || code > 57) && (code < 65 || code > 90)) {
return false;
}
}
return true;
}
function findUnescapedQuote(position) {
let escaped = false;
for (let index = position.start; index < position.text.length; index++) {
const char = position.text[index];
if (char === "\\") {
escaped = !escaped;
continue;
}
if (char === '"' && !escaped)
return index;
escaped = false;
}
return position.text.length;
}
function redactPrivateKeyBlocks(text) {
let cursor = 0;
let result = "";
while (cursor < text.length) {
const begin = text.indexOf(PEM_BEGIN, cursor);
if (begin === -1)
return result + text.slice(cursor);
const labelStart = begin + PEM_BEGIN.length;
const labelEnd = text.indexOf(PEM_DELIMITER, labelStart);
if (labelEnd === -1)
return result + text.slice(cursor);
const label = text.slice(labelStart, labelEnd);
if (!isPrivateKeyLabel(label)) {
const next = labelEnd + PEM_DELIMITER.length;
result += text.slice(cursor, next);
cursor = next;
continue;
}
const contentStart = labelEnd + PEM_DELIMITER.length;
const endMarker = `-----END ${label}-----`;
const end = text.indexOf(endMarker, contentStart);
result += text.slice(cursor, begin) + "[REDACTED:private-key]";
if (end === -1) {
cursor = findUnescapedQuote({ text, start: contentStart });
continue;
}
cursor = end + endMarker.length;
}
return result;
}
function redactSecrets(text) {
return redactJwtSecrets(redactTokenSecrets(redactPrivateKeyBlocks(text)));
}
// dist/clients/process-singletons.js
var SINGLETON_HOST_KEY = /* @__PURE__ */ Symbol.for("pi-lens.process-singletons.v1");
var SINGLETON_SCHEMA = "pi-lens.process-singletons";
var PROCESS_SINGLETON_RESET_KIND = "process-singleton-reset";
var singletonHost = globalThis;
function container() {
const existing = singletonHost[SINGLETON_HOST_KEY];
if (existing instanceof Map)
return existing;
const fresh = /* @__PURE__ */ new Map();
singletonHost[SINGLETON_HOST_KEY] = fresh;
return fresh;
}
function isAdoptable(cell, version) {
if (!cell || typeof cell !== "object")
return false;
const candidate = cell;
return candidate.schema === SINGLETON_SCHEMA && candidate.version === version && candidate.value !== void 0;
}
var PROCESS_SINGLETON_RESET_LOG_FAMILY = "process-singleton-reset-log";
var RESET_LOG_FAMILY = PROCESS_SINGLETON_RESET_LOG_FAMILY;
var RESET_LOG_VERSION = 1;
var RESET_LOG_CAP = 16;
function resetLog() {
return getProcessSingleton(RESET_LOG_FAMILY, RESET_LOG_VERSION, () => ({
entries: []
}));
}
function recordIncompatibleCell(family, wantedVersion, found) {
if (family === RESET_LOG_FAMILY)
return;
const log = resetLog();
if (log.entries.some((entry) => entry.family === family))
return;
if (log.entries.length >= RESET_LOG_CAP)
return;
log.entries.push({
family,
reason: `incompatible process singleton discarded (found schema=${String(found.schema)} version=${String(found.version)}, this build wants schema=${SINGLETON_SCHEMA} version=${wantedVersion})`
});
}
function getProcessSingletonResets() {
return resetLog().entries.map((entry) => ({ ...entry }));
}
function getProcessSingleton(family, version, create, onIncompatible) {
const cells = container();
const existing = cells.get(family);
if (isAdoptable(existing, version))
return existing.value;
if (existing !== void 0) {
recordIncompatibleCell(family, version, existing);
onIncompatible?.(existing.value);
}
const value = create();
cells.set(family, { schema: SINGLETON_SCHEMA, version, value });
return value;
}
var EVALUATION_ORDINAL_FAMILY = "module-evaluation-ordinal";
var EVALUATION_ORDINAL_VERSION = 1;
function nextModuleEvaluationOrdinal() {
const state2 = getProcessSingleton(EVALUATION_ORDINAL_FAMILY, EVALUATION_ORDINAL_VERSION, () => ({ evaluations: 0 }));
state2.evaluations += 1;
return state2.evaluations;
}
// dist/clients/probe-home-state.js
import * as fs from "node:fs";
import * as os from "node:os";
import * as path from "node:path";
var RESOLUTION_SLOT = /* @__PURE__ */ Symbol.for("pi-lens.probe-home-state.resolution");
function getProbeHomeResolution() {
return globalThis[RESOLUTION_SLOT];
}
function getProbeHomeRedirectEvent() {
return getProbeHomeResolution()?.event;
}
function getGlobalPiLensLogDir() {
const override = process.env.PI_LENS_HOME?.trim();
if (override)
return path.resolve(override);
return resolveProbeHomeDir() ?? path.join(os.homedir(), ".pi-lens");
}
function isTestProcessTargetingRealHome(file) {
if (!process.env.VITEST)
return false;
return isUnderRealDir(path.dirname(path.resolve(file)), path.join(os.homedir(), ".pi-lens"));
}
function resolveProbeHomeDir() {
const slot = globalThis;
const cached = slot[RESOLUTION_SLOT];
if (cached)
return cached.probeHome;
const cwd = process.cwd();
const probeHome = computeProbeHomeDir(cwd);
slot[RESOLUTION_SLOT] = {
probeHome,
event: probeHome ? { probeHome } : void 0
};
return probeHome;
}
function computeProbeHomeDir(cwd) {
if (process.env.PILENS_PROBE === "1") {
return path.join(cwd, ".pi-lens-probe-home");
}
const worktreeRoot = findAgentWorktreeRoot(cwd);
if (worktreeRoot)
return path.join(worktreeRoot, ".pi-lens-probe-home");
if (isUnderRealDir(cwd, os.tmpdir())) {
return path.join(cwd, ".pi-lens-probe-home");
}
return void 0;
}
function findAgentWorktreeRoot(cwd) {
const normalized = normalizeFilePath(path.resolve(cwd));
const match = /(?:^|\/)\.claude\/worktrees\/[^/]+(?:\/|$)/.exec(normalized);
if (!match)
return void 0;
return normalized.slice(0, match.index + match[0].length).replace(/\/$/, "");
}
function isUnderRealDir(child, parent) {
return isUnderDir(toRealPath(child), toRealPath(parent));
}
function toRealPath(target) {
try {
return fs.realpathSync.native(target);
} catch {
return target;
}
}
// dist/clients/ndjson-logger.js
import * as fs2 from "node:fs";
import * as path2 from "node:path";
function resolve3(v) {
return typeof v === "function" ? v() : v;
}
function runBestEffort(operation) {
try {
operation();
} catch {
return;
}
}
var NDJSON_GLOBAL_STATE_SCHEMA = "pi-lens.ndjson-logger.state";
var NDJSON_LEGACY_GLOBAL_STATE_VERSION = 1;
var NDJSON_GLOBAL_STATE_VERSION = 2;
var NDJSON_GLOBAL_STATE_KEY = /* @__PURE__ */ Symbol.for("pi-lens.ndjson-logger.state");
var globalStateHost = globalThis;
var existingGlobalState = globalStateHost[NDJSON_GLOBAL_STATE_KEY];
function isSharedWriterState(value) {
if (!value || typeof value !== "object")
return false;
const candidate = value;
const knownSchema = candidate.schema === void 0 || candidate.schema === NDJSON_GLOBAL_STATE_SCHEMA;
const knownVersion = candidate.version === void 0 || candidate.version === NDJSON_LEGACY_GLOBAL_STATE_VERSION || candidate.version === NDJSON_GLOBAL_STATE_VERSION;
return knownSchema && knownVersion && candidate.writers instanceof Map && candidate.exitFlushers instanceof Set && candidate.registeredLogFiles instanceof Set && typeof candidate.exitHandlerRegistered === "boolean";
}
function isLegacyGlobalState(value) {
if (!value || typeof value !== "object")
return false;
const candidate = value;
return candidate.exitFlushers instanceof Set && candidate.registeredLogFiles instanceof Set && typeof candidate.exitHandlerRegistered === "boolean";
}
var ndjsonGlobalState;
var legacyGlobalState;
if (isSharedWriterState(existingGlobalState)) {
const needsFlusherMigration = existingGlobalState.schema !== NDJSON_GLOBAL_STATE_SCHEMA || existingGlobalState.version !== NDJSON_GLOBAL_STATE_VERSION;
if (needsFlusherMigration) {
for (const state2 of existingGlobalState.writers.values()) {
const staleExitFlusher = state2.exitFlusher;
const currentExitFlusher = () => flushStateSync(state2);
state2.exitFlusher = currentExitFlusher;
existingGlobalState.exitFlushers.delete(staleExitFlusher);
existingGlobalState.exitFlushers.add(currentExitFlusher);
}
}
existingGlobalState.schema = NDJSON_GLOBAL_STATE_SCHEMA;
existingGlobalState.version = NDJSON_GLOBAL_STATE_VERSION;
ndjsonGlobalState = existingGlobalState;
} else if (existingGlobalState === void 0) {
ndjsonGlobalState = globalStateHost[NDJSON_GLOBAL_STATE_KEY] = {
schema: NDJSON_GLOBAL_STATE_SCHEMA,
version: NDJSON_GLOBAL_STATE_VERSION,
writers: /* @__PURE__ */ new Map(),
exitFlushers: /* @__PURE__ */ new Set(),
exitHandlerRegistered: false,
registeredLogFiles: /* @__PURE__ */ new Set()
};
} else if (isLegacyGlobalState(existingGlobalState)) {
legacyGlobalState = existingGlobalState;
}
var exitFlushers = ndjsonGlobalState?.exitFlushers ?? legacyGlobalState?.exitFlushers ?? /* @__PURE__ */ new Set();
var registeredLogFiles = ndjsonGlobalState?.registeredLogFiles ?? legacyGlobalState?.registeredLogFiles ?? /* @__PURE__ */ new Set();
function getRegisteredLogFiles() {
return registeredLogFiles;
}
function getSinkWriteFailures() {
if (!ndjsonGlobalState)
return [];
const result = [];
for (const state2 of ndjsonGlobalState.writers.values()) {
if (state2.writeFailures > 0) {
result.push({ file: state2.file, droppedCount: state2.writeFailures });
}
}
return result;
}
function resetSinkWriteFailures() {
if (!ndjsonGlobalState)
return;
for (const state2 of ndjsonGlobalState.writers.values())
state2.writeFailures = 0;
}
function getSinkRotations() {
if (!ndjsonGlobalState)
return [];
const result = [];
for (const state2 of ndjsonGlobalState.writers.values()) {
if (state2.rotationCount > 0 || state2.rotationFailures > 0) {
result.push({
file: state2.file,
rotationCount: state2.rotationCount,
failureCount: state2.rotationFailures
});
}
}
return result;
}
function resetSinkRotations() {
if (!ndjsonGlobalState)
return;
for (const state2 of ndjsonGlobalState.writers.values()) {
state2.rotationCount = 0;
state2.rotationFailures = 0;
state2.rotationRetryAfterMs = 0;
state2.optionConflictRecorded = false;
state2.truncateRefusals = 0;
}
}
function getSinkTruncateRefusals() {
if (!ndjsonGlobalState)
return [];
const result = [];
for (const state2 of ndjsonGlobalState.writers.values()) {
if (state2.truncateRefusals > 0) {
result.push({ file: state2.file, refusedCount: state2.truncateRefusals });
}
}
return result;
}
function getSinkOptionConflicts() {
if (!ndjsonGlobalState)
return [];
return [...ndjsonGlobalState.writers.values()].filter((state2) => state2.optionConflictRecorded).map((state2) => ({ file: state2.file }));
}
function requireCurrentGlobalState() {
if (ndjsonGlobalState)
return ndjsonGlobalState;
throw new Error("createNdjsonLogger: incompatible module graph state; a pre-7e4b9120 logger graph may still own private queues. Restart the process before loading the current logger graph");
}
function registerWriter(state2) {
const globalState = requireCurrentGlobalState();
if (!exitFlushers.has(state2.exitFlusher))
exitFlushers.add(state2.exitFlusher);
if (!globalState.exitHandlerRegistered) {
globalState.exitHandlerRegistered = true;
process.on("exit", () => {
for (const flush of exitFlushers)
runBestEffort(flush);
});
}
}
function normalizeLogPath(file) {
return normalizeFilePath(path2.resolve(file));
}
function writeQueueItemSync(state2, item) {
if (item.kind === "truncate") {
fs2.writeFileSync(state2.file, "");
noteTruncated(state2);
} else {
const plan = planBoundedWrite(state2, [item.line]);
fs2.appendFileSync(state2.file, plan.joined);
noteWriteSucceeded(state2, plan.bytes);
}
}
function applyQueueItemSync(state2, item) {
ensureDirSync(state2);
try {
writeQueueItemSync(state2, item);
return;
} catch {
}
state2.ensuredDir = false;
ensureDirSync(state2);
try {
writeQueueItemSync(state2, item);
} catch {
state2.writeFailures += 1;
}
}
function flushStateSync(state2) {
const inFlight = state2.inFlightBatch;
const inFlightAtHead = inFlight !== null && inFlight.every((item, index) => state2.queue[index] === item);
const repairStart = inFlightAtHead ? inFlight?.[0]?.kind === "truncate" ? 0 : state2.queue.findIndex((item) => item.kind === "truncate") : -1;
if (state2.syncRepairItems) {
state2.syncRepairItems.push(...state2.queue);
} else if (repairStart >= 0) {
state2.syncRepairItems = state2.queue.slice(repairStart);
}
while (state2.queue.length > 0) {
const item = state2.queue.shift();
applyQueueItemSync(state2, item);
}
}
function createWriterState(file, maxBytes, backupPath) {
const globalState = requireCurrentGlobalState();
const existing = globalState.writers.get(file);
if (existing) {
if (!exitFlushers.has(existing.exitFlusher))
registerWriter(existing);
if (typeof existing.writeFailures !== "number")
existing.writeFailures = 0;
if (typeof existing.writesSinceStatSync !== "number")
existing.writesSinceStatSync = 0;
if (typeof existing.lastStatSyncMs !== "number")
existing.lastStatSyncMs = 0;
if (typeof existing.rotationCount !== "number")
existing.rotationCount = 0;
if (typeof existing.rotationFailures !== "number")
existing.rotationFailures = 0;
if (typeof existing.rotationRetryAfterMs !== "number")
existing.rotationRetryAfterMs = 0;
if (typeof existing.optionConflictRecorded !== "boolean")
existing.optionConflictRecorded = false;
if (typeof existing.truncateRefusals !== "number")
existing.truncateRefusals = 0;
const optionsDiffer = existing.maxBytes !== maxBytes || existing.backupPath !== backupPath;
if (optionsDiffer) {
if (existing.maxBytes === void 0 && maxBytes !== void 0)
existing.maxBytes = maxBytes;
if (existing.backupPath === void 0 && backupPath !== void 0)
existing.backupPath = backupPath;
existing.optionConflictRecorded = true;
}
return existing;
}
const state2 = {};
state2.file = file;
state2.maxBytes = maxBytes;
state2.backupPath = backupPath;
state2.queue = [];
state2.drainPromise = null;
state2.inFlightBatch = null;
state2.syncRepairItems = null;
state2.ensuredDir = false;
state2.exitFlusher = () => flushStateSync(state2);
state2.writeFailures = 0;
state2.knownSize = void 0;
state2.writesSinceStatSync = 0;
state2.lastStatSyncMs = 0;
state2.rotationCount = 0;
state2.rotationFailures = 0;
state2.rotationRetryAfterMs = 0;
state2.optionConflictRecorded = false;
state2.truncateRefusals = 0;
globalState.writers.set(file, state2);
registerWriter(state2);
return state2;
}
function ensureDirSync(state2) {
if (state2.ensuredDir)
return;
runBestEffort(() => {
fs2.mkdirSync(path2.dirname(state2.file), { recursive: true });
state2.ensuredDir = true;
});
}
async function ensureDirAsync(state2) {
if (state2.ensuredDir)
return;
try {
await fs2.promises.mkdir(path2.dirname(state2.file), { recursive: true });
state2.ensuredDir = true;
} catch {
}
}
var ROTATION_STAT_RESYNC_WRITES = 25;
var ROTATION_STAT_RESYNC_MS = 2e3;
function syncKnownSizeIfDue(state2, now) {
const due = state2.knownSize === void 0 || state2.writesSinceStatSync >= ROTATION_STAT_RESYNC_WRITES || now - state2.lastStatSyncMs >= ROTATION_STAT_RESYNC_MS;
if (!due)
return;
state2.writesSinceStatSync = 0;
state2.lastStatSyncMs = now;
try {
state2.knownSize = fs2.statSync(state2.file).size;
} catch {
state2.knownSize = 0;
}
}
var ROTATION_FAILURE_BACKOFF_MS = 1e4;
function rotateNow(state2, incomingBytes, now) {
if (state2.maxBytes === void 0)
return false;
if (now < state2.rotationRetryAfterMs)
return false;
let actualSize;
try {
actualSize = fs2.statSync(state2.file).size;
} catch {
state2.knownSize = 0;
state2.writesSinceStatSync = 0;
state2.lastStatSyncMs = now;
return false;
}
state2.knownSize = actualSize;
state2.writesSinceStatSync = 0;
state2.lastStatSyncMs = now;
if (actualSize === 0)
return false;
if (actualSize + incomingBytes < state2.maxBytes)
return false;
const backup = state2.backupPath ?? `${state2.file}.1`;
try {
runBestEffort(() => fs2.rmSync(backup, { force: true }));
fs2.renameSync(state2.file, backup);
} catch {
state2.rotationFailures += 1;
state2.rotationRetryAfterMs = now + ROTATION_FAILURE_BACKOFF_MS;
return false;
}
state2.knownSize = 0;
state2.rotationCount += 1;
state2.rotationRetryAfterMs = 0;
return true;
}
function takeAll(lines) {
const joined = lines.join("");
return { count: lines.length, joined, bytes: Buffer.byteLength(joined) };
}
function planBoundedWrite(state2, lines) {
if (state2.maxBytes === void 0 || lines.length === 0)
return takeAll(lines);
const now = Date.now();
syncKnownSizeIfDue(state2, now);
state2.writesSinceStatSync += 1;
const firstBytes = Buffer.byteLength(lines[0] ?? "");
let known = state2.knownSize ?? 0;
if (known > 0 && known + firstBytes >= state2.maxBytes) {
rotateNow(state2, firstBytes, now);
known = state2.knownSize ?? 0;
if (known > 0 && known + firstBytes >= state2.maxBytes) {
return takeAll(lines);
}
}
let bytes = 0;
let count = 0;
for (const line of lines) {
const lineBytes = Buffer.byteLength(line);
if (count > 0 && known + bytes + lineBytes >= state2.maxBytes)
break;
bytes += lineBytes;
count += 1;
}
if (count === lines.length)
return takeAll(lines);
return { count, joined: lines.slice(0, count).join(""), bytes };
}
function noteWriteSucceeded(state2, bytes) {
if (state2.maxBytes === void 0)
return;
state2.knownSize = (state2.knownSize ?? 0) + bytes;
}
function noteTruncated(state2) {
if (state2.maxBytes === void 0)
return;
state2.knownSize = 0;
state2.writesSinceStatSync = 0;
state2.lastStatSyncMs = Date.now();
}
async function writeQueueItemAsync(state2, item) {
if (item.kind === "truncate") {
await fs2.promises.writeFile(state2.file, "");
noteTruncated(state2);
} else {
const plan = planBoundedWrite(state2, [item.line]);
await fs2.promises.appendFile(state2.file, plan.joined);
noteWriteSucceeded(state2, plan.bytes);
}
}
async function applyQueueItemAsync(state2, item) {
await ensureDirAsync(state2);
try {
await writeQueueItemAsync(state2, item);
return;
} catch {
}
state2.ensuredDir = false;
await ensureDirAsync(state2);
try {
await writeQueueItemAsync(state2, item);
} catch {
state2.writeFailures += 1;
}
}
async function drainLoop(state2) {
while (state2.queue.length > 0) {
const item = state2.queue[0];
await ensureDirAsync(state2);
const truncateIndex = item.kind === "line" ? state2.queue.findIndex((queued) => queued.kind === "truncate") : 0;
const pendingEnd = truncateIndex === -1 ? state2.queue.length : truncateIndex;
const offered = item.kind === "truncate" ? [item] : state2.queue.slice(0, pendingEnd);
const plan = item.kind === "truncate" ? null : planBoundedWrite(state2, offered.map((queued) => queued.line));
const pending = plan === null ? offered : offered.slice(0, plan.count);
state2.inFlightBatch = pending;
const writeBatch = async () => {
if (plan === null) {
await fs2.promises.writeFile(state2.file, "");
noteTruncated(state2);
} else {
await fs2.promises.appendFile(state2.file, plan.joined);
noteWriteSucceeded(state2, plan.bytes);
}
};
try {
await writeBatch();
} catch {
state2.ensuredDir = false;
await ensureDirAsync(state2);
try {
await writeBatch();
} catch {
state2.writeFailures += pending.length;
}
}
for (const written of pending) {
if (state2.queue[0] !== written)
break;
state2.queue.shift();
}
if (state2.inFlightBatch === pending) {
state2.inFlightBatch = null;
const repairItems = state2.syncRepairItems;
state2.syncRepairItems = null;
if (repairItems) {
for (const repairItem of repairItems) {
await applyQueueItemAsync(state2, repairItem);
}
}
}
}
}
function drain(state2) {
if (!state2.drainPromise) {
state2.drainPromise = Promise.resolve().then(() => drainLoop(state2)).finally(() => {
state2.drainPromise = null;
});
}
return state2.drainPromise;
}
function createNdjsonLogger(options) {
const states = /* @__PURE__ */ new Set();
const canonicalPaths = /* @__PURE__ */ new Map();
const staticFile = typeof options.filePath === "string" ? normalizeLogPath(options.filePath) : void 0;
const staticBackupPath = typeof options.backupPath === "string" && options.maxBytes !== void 0 ? normalizeLogPath(options.backupPath) : void 0;
function canonicalPath(file) {
const raw = path2.resolve(file);
const cached = canonicalPaths.get(raw);
if (cached)
return cached;
const normalized = normalizeLogPath(raw);
canonicalPaths.set(raw, normalized);
return normalized;
}
function stateForCall() {
const file = staticFile ?? canonicalPath(resolve3(options.filePath));
const backupPath = options.maxBytes !== void 0 && options.backupPath ? staticBackupPath ?? canonicalPath(resolve3(options.backupPath)) : void 0;
const state2 = createWriterState(file, options.maxBytes, backupPath);
states.add(state2);
return state2;
}
if (staticFile !== void 0) {
const state2 = stateForCall();
registeredLogFiles.add(state2.file);
}
function enqueue(item) {
const state2 = stateForCall();
state2.queue.push(item);
void drain(state2);
}
return {
log(obj) {
const serialized = String(JSON.stringify(obj));
enqueue({
kind: "line",
line: `${redactSecrets(serialized)}
`
});
},
append(line) {
enqueue({ kind: "line", line: `${redactSecrets(line)}
` });
},
truncate() {
const state2 = stateForCall();
if (isTestProcessTargetingRealHome(state2.file)) {
if (state2.truncateRefusals === 0) {
try {
process.emitWarning(`pi-lens refused to truncate the real-home log ${state2.file} from a test process (pin PI_LENS_HOME for this file)`, { code: "PI_LENS_LOG_SINK_TRUNCATE_REFUSED" });
} catch {
}
}
state2.truncateRefusals += 1;
return;
}
enqueue({ kind: "truncate" });
},
async flush() {
await Promise.all([...states].map((state2) => drain(state2)));
},
flushSync() {
for (const state2 of states)
flushStateSync(state2);
}
};
}
// dist/clients/log-cleanup.js
import * as fs3 from "node:fs";
import * as path3 from "node:path";
var LOG_DIR = getGlobalPiLensLogDir();
var LOGS_SUBDIR = path3.join(LOG_DIR, "logs");
function getManagedLogFiles(dir = LOG_DIR) {
const names = /* @__PURE__ */ new Set();
for (const absPath of getRegisteredLogFiles()) {
if (pathsEqual(path3.dirname(absPath), dir)) {
names.add(path3.basename(absPath));
}
}
try {
if (fs3.existsSync(dir)) {
for (const entry of fs3.readdirSync(dir)) {
if (entry.endsWith(".log") && !ROTATED_BACKUP_RE.test(entry)) {
names.add(entry);
}
}
}
} catch {
}
return [...names].sort((a, b) => a.localeCompare(b));
}
var ROTATED_BACKUP_RE = /(\.\d{4}-\d{2}-\d{2}T.*\.log|\.log\..+)$/;
function getMaxLogSizeMB() {
return Math.max(1, Number.parseInt(process.env.PI_LENS_MAX_LOG_SIZE_MB ?? "10", 10) || 10);
}
function getConfig() {
return {
retentionDays: Math.max(1, Number.parseInt(process.env.PI_LENS_LOG_RETENTION_DAYS ?? "7", 10) || 7),
maxSizeMB: getMaxLogSizeMB()
};
}
function getFileAgeDays(filePath) {
try {
const stats = fs3.statSync(filePath);
const ageMs = Date.now() - stats.mtime.getTime();
return ageMs / (1e3 * 60 * 60 * 24);
} catch {
return 0;
}
}
function getFileSizeMB(filePath) {
try {
const stats = fs3.statSync(filePath);
return stats.size / (1024 * 1024);
} catch {
return 0;
}
}
function cleanupOldLogs(directory, pattern, retentionDays) {
const config = getConfig();
const maxAge = retentionDays ?? config.retentionDays;
const deleted = [];
const errors = [];
try {
if (!fs3.existsSync(directory)) {
return { deleted, errors };
}
const files = fs3.readdirSync(directory);
for (const file of files) {
if (!pattern.test(file))
continue;
const filePath = path3.join(directory, file);
const ageDays = getFileAgeDays(filePath);
if (ageDays > maxAge) {
try {
fs3.unlinkSync(filePath);
deleted.push(file);
} catch (err) {
errors.push(`${file}: ${err}`);
}
}
}
} catch (err) {
errors.push(`Directory read failed: ${err}`);
}
return { deleted, errors };
}
function rotateLogIfNeeded(logFile, maxSizeMB) {
const config = getConfig();
const maxSize = maxSizeMB ?? config.maxSizeMB;
const sizeMB = getFileSizeMB(logFile);
if (sizeMB < maxSize) {
return { rotated: false };
}
try {
const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
const ext = path3.extname(logFile);
const base = logFile.slice(0, -ext.length);
const backupFile = `${base}.${timestamp}${ext}`;
fs3.renameSync(logFile, backupFile);
fs3.writeFileSync(logFile, "", "utf8");
return { rotated: true, newFile: backupFile };
} catch {
return { rotated: false };
}
}
function runLogCleanup(dbg) {
const config = getConfig();
const results = {
cleaned: 0,
rotated: 0,
report: ""
};
const dailyLogs = cleanupOldLogs(LOGS_SUBDIR, /\.jsonl$/, config.retentionDays);
results.cleaned += dailyLogs.deleted.length;
const rotatedLogs = cleanupOldLogs(LOG_DIR, ROTATED_BACKUP_RE, config.retentionDays);
results.cleaned += rotatedLogs.deleted.length;
const mainLogs = getManagedLogFiles().map((name) => path3.join(LOG_DIR, name));
for (const logFile of mainLogs) {
const rotation = rotateLogIfNeeded(logFile, config.maxSizeMB);
if (rotation.rotated) {
results.rotated++;
if (rotation.newFile) {
const sizeMB = getFileSizeMB(rotation.newFile);
dbg?.(`log_cleanup: rotated ${path3.basename(logFile)} (${sizeMB.toFixed(1)}MB) \u2192 ${path3.basename(rotation.newFile)}`);
}
}
}
const parts = [];
if (dailyLogs.deleted.length > 0) {
parts.push(`${dailyLogs.deleted.length} daily logs`);
}
if (rotatedLogs.deleted.length > 0) {
parts.push(`${rotatedLogs.deleted.length} rotated logs`);
}
if (results.rotated > 0) {
parts.push(`${results.rotated} active logs rotated`);
}
results.report = parts.length > 0 ? `log_cleanup: removed ${parts.join(", ")} (retention: ${config.retentionDays}d, maxSize: ${config.maxSizeMB}MB)` : `log_cleanup: no cleanup needed (retention: ${config.retentionDays}d, maxSize: ${config.maxSizeMB}MB)`;
if (dailyLogs.errors.length > 0 || rotatedLogs.errors.length > 0) {
dbg?.(`log_cleanup errors: ${[...dailyLogs.errors, ...rotatedLogs.errors].join("; ")}`);
}
return results;
}
// dist/clients/turn-context.js
import { AsyncLocalStorage } from "node:async_hooks";
var FAMILY = "turn-context";
var VERSION = 2;
function state() {
return getProcessSingleton(FAMILY, VERSION, () => ({
sessions: /* @__PURE__ */ new Map(),
activeSession: new AsyncLocalStorage()
}));
}
function sessionState(sessionId) {
const current = state();
let session = current.sessions.get(sessionId);
if (!session) {
session = { turnId: `${sessionId}:0`, turnIndex: 0 };
current.sessions.set(sessionId, session);
}
return session;
}
function setTurnContextSession(sessionId) {
const stableSessionId = sessionId?.trim();
if (stableSessionId !== void 0 && stableSessionId !== "")
sessionState(stableSessionId);
}
function beginTurnContext(sessionId) {
const session = sessionState(sessionId);
session.turnIndex += 1;
session.turnId = `${sessionId}:${session.turnIndex}`;
return session.turnId;
}
function getTurnId() {
const current = state();
const sessionId = current.activeSession.getStore();
if (sessionId === void 0)
return "turn:0";
return sessionState(sessionId).turnId;
}
export {
isTestMode,
toPositiveFinite,
lazyEnvNumber,
getProbeHomeRedirectEvent,
getGlobalPiLensLogDir,
redactSecrets,
getSinkWriteFailures,
resetSinkWriteFailures,
getSinkRotations,
resetSinkRotations,
getSinkTruncateRefusals,
getSinkOptionConflicts,
createNdjsonLogger,
getMaxLogSizeMB,
runLogCleanup,
PROCESS_SINGLETON_RESET_KIND,
getProcessSingletonResets,
getProcessSingleton,
nextModuleEvaluationOrdinal,
setTurnContextSession,
beginTurnContext,
getTurnId
};