pi-agent-browser-native 0.2.48 → 0.2.49
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/CHANGELOG.md +17 -0
- package/README.md +16 -6
- package/dist/extensions/agent-browser/index.js +785 -0
- package/dist/extensions/agent-browser/lib/argv-descriptor.js +71 -0
- package/dist/extensions/agent-browser/lib/argv-grammar.js +121 -0
- package/dist/extensions/agent-browser/lib/bash-guard.js +190 -0
- package/dist/extensions/agent-browser/lib/command-policy.js +85 -0
- package/dist/extensions/agent-browser/lib/command-taxonomy.js +302 -0
- package/dist/extensions/agent-browser/lib/config-policy.js +686 -0
- package/dist/extensions/agent-browser/lib/config.js +122 -0
- package/dist/extensions/agent-browser/lib/electron/cdp.js +51 -0
- package/dist/extensions/agent-browser/lib/electron/cleanup.js +212 -0
- package/dist/extensions/agent-browser/lib/electron/discovery.js +633 -0
- package/dist/extensions/agent-browser/lib/electron/launch.js +351 -0
- package/{extensions/agent-browser/lib/electron/text.ts → dist/extensions/agent-browser/lib/electron/text.js} +5 -5
- package/dist/extensions/agent-browser/lib/executable-path.js +20 -0
- package/dist/extensions/agent-browser/lib/fs-utils.js +18 -0
- package/dist/extensions/agent-browser/lib/input-modes/electron.js +165 -0
- package/dist/extensions/agent-browser/lib/input-modes/job.js +519 -0
- package/dist/extensions/agent-browser/lib/input-modes/lookups.js +440 -0
- package/dist/extensions/agent-browser/lib/input-modes/params.js +164 -0
- package/dist/extensions/agent-browser/lib/input-modes/semantic-action.js +119 -0
- package/dist/extensions/agent-browser/lib/input-modes/shared.js +42 -0
- package/dist/extensions/agent-browser/lib/input-modes/types.js +21 -0
- package/dist/extensions/agent-browser/lib/input-modes.js +10 -0
- package/dist/extensions/agent-browser/lib/json-schema.js +58 -0
- package/dist/extensions/agent-browser/lib/launch-scoped-flags.js +59 -0
- package/dist/extensions/agent-browser/lib/navigation-policy.js +83 -0
- package/dist/extensions/agent-browser/lib/orchestration/batch-stdin.js +62 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/artifact-paths.js +39 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/click-dispatch.js +276 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/diagnostics.js +909 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/final-result.js +443 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/index.js +47 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare/direct-anchor-download.js +141 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare/network-page-filter.js +108 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare/scroll-shims.js +112 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare/snapshot-filter.js +158 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare/wait-timeouts.js +54 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare.js +762 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/process-output.js +491 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/prompt-guards.js +40 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/session-artifacts.js +5 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/session-state.js +731 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/types.js +1 -0
- package/dist/extensions/agent-browser/lib/orchestration/electron-host/index.js +718 -0
- package/dist/extensions/agent-browser/lib/orchestration/input-plan.js +247 -0
- package/dist/extensions/agent-browser/lib/orchestration/output-file.js +68 -0
- package/{extensions/agent-browser/lib/parsing.ts → dist/extensions/agent-browser/lib/parsing.js} +12 -11
- package/dist/extensions/agent-browser/lib/pi-tool-rendering.js +241 -0
- package/dist/extensions/agent-browser/lib/playbook.js +121 -0
- package/dist/extensions/agent-browser/lib/process.js +448 -0
- package/dist/extensions/agent-browser/lib/prompt-policy.js +91 -0
- package/dist/extensions/agent-browser/lib/results/action-recommendations.js +220 -0
- package/dist/extensions/agent-browser/lib/results/artifact-manifest.js +111 -0
- package/{extensions/agent-browser/lib/results/artifact-state.ts → dist/extensions/agent-browser/lib/results/artifact-state.js} +4 -8
- package/dist/extensions/agent-browser/lib/results/categories.js +76 -0
- package/dist/extensions/agent-browser/lib/results/confirmation.js +63 -0
- package/dist/extensions/agent-browser/lib/results/contracts.js +8 -0
- package/dist/extensions/agent-browser/lib/results/editable-ref-evidence.js +74 -0
- package/dist/extensions/agent-browser/lib/results/envelope.js +166 -0
- package/dist/extensions/agent-browser/lib/results/network-routes.js +92 -0
- package/dist/extensions/agent-browser/lib/results/network.js +73 -0
- package/dist/extensions/agent-browser/lib/results/next-actions.js +72 -0
- package/dist/extensions/agent-browser/lib/results/presentation/artifacts.js +515 -0
- package/dist/extensions/agent-browser/lib/results/presentation/batch.js +397 -0
- package/dist/extensions/agent-browser/lib/results/presentation/browser-profile-recovery.js +55 -0
- package/dist/extensions/agent-browser/lib/results/presentation/common.js +46 -0
- package/dist/extensions/agent-browser/lib/results/presentation/content.js +24 -0
- package/dist/extensions/agent-browser/lib/results/presentation/diagnostics.js +960 -0
- package/dist/extensions/agent-browser/lib/results/presentation/errors.js +205 -0
- package/dist/extensions/agent-browser/lib/results/presentation/large-output.js +134 -0
- package/dist/extensions/agent-browser/lib/results/presentation/navigation.js +159 -0
- package/dist/extensions/agent-browser/lib/results/presentation/registry.js +216 -0
- package/dist/extensions/agent-browser/lib/results/presentation/semantic-action.js +104 -0
- package/dist/extensions/agent-browser/lib/results/presentation/skills.js +152 -0
- package/dist/extensions/agent-browser/lib/results/presentation.js +177 -0
- package/dist/extensions/agent-browser/lib/results/recovery-actions.js +107 -0
- package/dist/extensions/agent-browser/lib/results/recovery-next-actions.js +50 -0
- package/dist/extensions/agent-browser/lib/results/selector-recovery.js +225 -0
- package/{extensions/agent-browser/lib/results/shared.ts → dist/extensions/agent-browser/lib/results/shared.js} +0 -1
- package/dist/extensions/agent-browser/lib/results/snapshot-high-value-controls.js +208 -0
- package/dist/extensions/agent-browser/lib/results/snapshot-refs.js +78 -0
- package/dist/extensions/agent-browser/lib/results/snapshot-segments.js +331 -0
- package/dist/extensions/agent-browser/lib/results/snapshot-spill.js +40 -0
- package/dist/extensions/agent-browser/lib/results/snapshot.js +264 -0
- package/dist/extensions/agent-browser/lib/results/text.js +40 -0
- package/{extensions/agent-browser/lib/results.ts → dist/extensions/agent-browser/lib/results.js} +2 -32
- package/dist/extensions/agent-browser/lib/runtime.js +816 -0
- package/dist/extensions/agent-browser/lib/session-page-state.js +411 -0
- package/dist/extensions/agent-browser/lib/string-enum-schema.js +13 -0
- package/dist/extensions/agent-browser/lib/temp.js +498 -0
- package/dist/extensions/agent-browser/lib/web-search.js +562 -0
- package/docs/RELEASE.md +22 -11
- package/docs/SUPPORT_MATRIX.md +4 -3
- package/package.json +9 -5
- package/scripts/config.mjs +8 -2
- package/scripts/doctor.mjs +8 -7
- package/extensions/agent-browser/index.ts +0 -961
- package/extensions/agent-browser/lib/argv-descriptor.ts +0 -90
- package/extensions/agent-browser/lib/argv-grammar.ts +0 -128
- package/extensions/agent-browser/lib/bash-guard.ts +0 -205
- package/extensions/agent-browser/lib/command-policy.ts +0 -71
- package/extensions/agent-browser/lib/command-taxonomy.ts +0 -336
- package/extensions/agent-browser/lib/config-policy.js +0 -690
- package/extensions/agent-browser/lib/config.ts +0 -211
- package/extensions/agent-browser/lib/electron/cdp.ts +0 -69
- package/extensions/agent-browser/lib/electron/cleanup.ts +0 -235
- package/extensions/agent-browser/lib/electron/discovery.ts +0 -710
- package/extensions/agent-browser/lib/electron/launch.ts +0 -499
- package/extensions/agent-browser/lib/executable-path.ts +0 -19
- package/extensions/agent-browser/lib/fs-utils.ts +0 -18
- package/extensions/agent-browser/lib/input-modes/electron.ts +0 -170
- package/extensions/agent-browser/lib/input-modes/job.ts +0 -527
- package/extensions/agent-browser/lib/input-modes/lookups.ts +0 -447
- package/extensions/agent-browser/lib/input-modes/params.ts +0 -205
- package/extensions/agent-browser/lib/input-modes/semantic-action.ts +0 -127
- package/extensions/agent-browser/lib/input-modes/shared.ts +0 -46
- package/extensions/agent-browser/lib/input-modes/types.ts +0 -225
- package/extensions/agent-browser/lib/input-modes.ts +0 -45
- package/extensions/agent-browser/lib/json-schema.ts +0 -73
- package/extensions/agent-browser/lib/launch-scoped-flags.ts +0 -67
- package/extensions/agent-browser/lib/navigation-policy.ts +0 -95
- package/extensions/agent-browser/lib/orchestration/batch-stdin.ts +0 -65
- package/extensions/agent-browser/lib/orchestration/browser-run/artifact-paths.ts +0 -44
- package/extensions/agent-browser/lib/orchestration/browser-run/click-dispatch.ts +0 -280
- package/extensions/agent-browser/lib/orchestration/browser-run/diagnostics.ts +0 -914
- package/extensions/agent-browser/lib/orchestration/browser-run/final-result.ts +0 -521
- package/extensions/agent-browser/lib/orchestration/browser-run/index.ts +0 -53
- package/extensions/agent-browser/lib/orchestration/browser-run/prepare/direct-anchor-download.ts +0 -158
- package/extensions/agent-browser/lib/orchestration/browser-run/prepare/network-page-filter.ts +0 -116
- package/extensions/agent-browser/lib/orchestration/browser-run/prepare/scroll-shims.ts +0 -147
- package/extensions/agent-browser/lib/orchestration/browser-run/prepare/snapshot-filter.ts +0 -183
- package/extensions/agent-browser/lib/orchestration/browser-run/prepare/wait-timeouts.ts +0 -58
- package/extensions/agent-browser/lib/orchestration/browser-run/prepare.ts +0 -847
- package/extensions/agent-browser/lib/orchestration/browser-run/process-output.ts +0 -559
- package/extensions/agent-browser/lib/orchestration/browser-run/prompt-guards.ts +0 -47
- package/extensions/agent-browser/lib/orchestration/browser-run/session-artifacts.ts +0 -8
- package/extensions/agent-browser/lib/orchestration/browser-run/session-state.ts +0 -868
- package/extensions/agent-browser/lib/orchestration/browser-run/types.ts +0 -565
- package/extensions/agent-browser/lib/orchestration/electron-host/index.ts +0 -855
- package/extensions/agent-browser/lib/orchestration/input-plan.ts +0 -375
- package/extensions/agent-browser/lib/orchestration/output-file.ts +0 -86
- package/extensions/agent-browser/lib/pi-tool-rendering.ts +0 -267
- package/extensions/agent-browser/lib/playbook.ts +0 -142
- package/extensions/agent-browser/lib/process.ts +0 -516
- package/extensions/agent-browser/lib/prompt-policy.ts +0 -105
- package/extensions/agent-browser/lib/results/action-recommendations.ts +0 -264
- package/extensions/agent-browser/lib/results/artifact-manifest.ts +0 -111
- package/extensions/agent-browser/lib/results/categories.ts +0 -106
- package/extensions/agent-browser/lib/results/confirmation.ts +0 -76
- package/extensions/agent-browser/lib/results/contracts.ts +0 -241
- package/extensions/agent-browser/lib/results/editable-ref-evidence.ts +0 -72
- package/extensions/agent-browser/lib/results/envelope.ts +0 -195
- package/extensions/agent-browser/lib/results/network-routes.ts +0 -83
- package/extensions/agent-browser/lib/results/network.ts +0 -78
- package/extensions/agent-browser/lib/results/next-actions.ts +0 -117
- package/extensions/agent-browser/lib/results/presentation/artifacts.ts +0 -588
- package/extensions/agent-browser/lib/results/presentation/batch.ts +0 -450
- package/extensions/agent-browser/lib/results/presentation/browser-profile-recovery.ts +0 -67
- package/extensions/agent-browser/lib/results/presentation/common.ts +0 -53
- package/extensions/agent-browser/lib/results/presentation/content.ts +0 -36
- package/extensions/agent-browser/lib/results/presentation/diagnostics.ts +0 -923
- package/extensions/agent-browser/lib/results/presentation/errors.ts +0 -227
- package/extensions/agent-browser/lib/results/presentation/large-output.ts +0 -182
- package/extensions/agent-browser/lib/results/presentation/navigation.ts +0 -184
- package/extensions/agent-browser/lib/results/presentation/registry.ts +0 -242
- package/extensions/agent-browser/lib/results/presentation/semantic-action.ts +0 -131
- package/extensions/agent-browser/lib/results/presentation/skills.ts +0 -143
- package/extensions/agent-browser/lib/results/presentation.ts +0 -257
- package/extensions/agent-browser/lib/results/recovery-actions.ts +0 -139
- package/extensions/agent-browser/lib/results/recovery-next-actions.ts +0 -71
- package/extensions/agent-browser/lib/results/selector-recovery.ts +0 -320
- package/extensions/agent-browser/lib/results/snapshot-high-value-controls.ts +0 -273
- package/extensions/agent-browser/lib/results/snapshot-refs.ts +0 -100
- package/extensions/agent-browser/lib/results/snapshot-segments.ts +0 -366
- package/extensions/agent-browser/lib/results/snapshot-spill.ts +0 -63
- package/extensions/agent-browser/lib/results/snapshot.ts +0 -329
- package/extensions/agent-browser/lib/results/text.ts +0 -40
- package/extensions/agent-browser/lib/runtime.ts +0 -988
- package/extensions/agent-browser/lib/session-page-state.ts +0 -512
- package/extensions/agent-browser/lib/string-enum-schema.ts +0 -20
- package/extensions/agent-browser/lib/temp.ts +0 -577
- package/extensions/agent-browser/lib/web-search.ts +0 -728
- /package/{extensions/agent-browser/lib/orchestration/browser-run.ts → dist/extensions/agent-browser/lib/orchestration/browser-run.js} +0 -0
|
@@ -1,577 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Purpose: Create private temporary and persisted spill files for the pi-agent-browser extension without leaking artifacts broadly on disk.
|
|
3
|
-
* Responsibilities: Maintain a process-private temp root, stamp explicit ownership/protected-child markers, enforce an aggregate temp-artifact disk budget, create securely permissioned temp files, create session-scoped persisted spill files for resumable sessions, prune explicitly owned stale temp roots from prior runs without deleting protected children, and best-effort clean all owned roots on process exit.
|
|
4
|
-
* Scope: Artifact lifecycle helpers only; callers decide what data to write and when to delete or retain long-lived references.
|
|
5
|
-
* Usage: Imported by result/process helpers when they need secure spill files instead of world-readable shared tmp paths.
|
|
6
|
-
* Invariants/Assumptions: Temp artifacts live under the OS temp directory, each active run uses a dedicated 0700 directory, files are created with exclusive 0600 permissions, session-scoped persisted artifacts stay under the pi session directory, and stale pruning only touches roots with an explicit pi-agent-browser ownership marker.
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
import { execFile } from "node:child_process";
|
|
10
|
-
import { randomBytes } from "node:crypto";
|
|
11
|
-
import { existsSync, readdirSync, rmSync } from "node:fs";
|
|
12
|
-
import { chmod, mkdir, mkdtemp, open, readFile, readdir, rm, stat, writeFile } from "node:fs/promises";
|
|
13
|
-
import { tmpdir } from "node:os";
|
|
14
|
-
import { basename, dirname, join, resolve } from "node:path";
|
|
15
|
-
import { promisify } from "node:util";
|
|
16
|
-
|
|
17
|
-
import { isRecord, parsePositiveInteger } from "./parsing.js";
|
|
18
|
-
|
|
19
|
-
const TEMP_ROOT_PREFIX = "pi-agent-browser-";
|
|
20
|
-
const TEMP_ROOT_MARKER_FILE_NAME = ".pi-agent-browser-owner.json";
|
|
21
|
-
const TEMP_ROOT_MARKER_KIND = "pi-agent-browser-temp-root";
|
|
22
|
-
const TEMP_ROOT_MARKER_VERSION = 1;
|
|
23
|
-
const STALE_TEMP_ROOT_MAX_AGE_MS = 24 * 60 * 60 * 1_000;
|
|
24
|
-
const TEMP_ROOT_MAX_BYTES_ENV = "PI_AGENT_BROWSER_TEMP_ROOT_MAX_BYTES";
|
|
25
|
-
const DEFAULT_TEMP_ROOT_MAX_BYTES = 32 * 1_024 * 1_024;
|
|
26
|
-
const SESSION_ARTIFACT_MAX_BYTES_ENV = "PI_AGENT_BROWSER_SESSION_ARTIFACT_MAX_BYTES";
|
|
27
|
-
const DEFAULT_SESSION_ARTIFACT_MAX_BYTES = 32 * 1_024 * 1_024;
|
|
28
|
-
const SESSION_ARTIFACTS_ROOT_DIR_NAME = ".pi-agent-browser-artifacts";
|
|
29
|
-
const PROCESS_START_IDENTITY_TIMEOUT_MS = 1_000;
|
|
30
|
-
const execFileAsync = promisify(execFile);
|
|
31
|
-
|
|
32
|
-
export interface PersistentSessionArtifactStore {
|
|
33
|
-
protectedPaths?: readonly string[];
|
|
34
|
-
sessionDir: string;
|
|
35
|
-
sessionId: string;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
export interface PersistentSessionArtifactEviction {
|
|
39
|
-
mtimeMs: number;
|
|
40
|
-
path: string;
|
|
41
|
-
sizeBytes: number;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
export interface PersistentSessionArtifactWriteResult {
|
|
45
|
-
evictedArtifacts: PersistentSessionArtifactEviction[];
|
|
46
|
-
path: string;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
interface TempRootOwnershipRecord {
|
|
50
|
-
createdAtMs: number;
|
|
51
|
-
kind: string;
|
|
52
|
-
leaseUpdatedAtMs?: number;
|
|
53
|
-
ownerPid?: number;
|
|
54
|
-
ownerProcessStartIdentity?: string;
|
|
55
|
-
ownerUid?: number;
|
|
56
|
-
protectedChildNames?: readonly string[];
|
|
57
|
-
version: number;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
interface TempRootOwnershipMarkerOptions {
|
|
61
|
-
createdAtMs?: number;
|
|
62
|
-
leaseUpdatedAtMs?: number;
|
|
63
|
-
ownerPid?: number;
|
|
64
|
-
ownerProcessStartIdentity?: string;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
type ProcessLiveness = "alive" | "dead" | "unknown";
|
|
68
|
-
|
|
69
|
-
let sessionTempRootPromise: Promise<string> | undefined;
|
|
70
|
-
let exitCleanupRegistered = false;
|
|
71
|
-
let tempMutationQueue = Promise.resolve();
|
|
72
|
-
const ownedTempRoots = new Set<string>();
|
|
73
|
-
const protectedTempChildren = new Set<string>();
|
|
74
|
-
|
|
75
|
-
function getCurrentProcessUid(): number | undefined {
|
|
76
|
-
return typeof process.getuid === "function" ? process.getuid() : undefined;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
function isPositiveFiniteNumber(value: unknown): value is number {
|
|
80
|
-
return typeof value === "number" && Number.isFinite(value) && value > 0;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
function isProtectedTempChildName(value: unknown): value is string {
|
|
84
|
-
if (typeof value !== "string") return false;
|
|
85
|
-
if (value === "" || value === "." || value === ".." || value === TEMP_ROOT_MARKER_FILE_NAME) return false;
|
|
86
|
-
if (value.includes("/") || value.includes("\\")) return false;
|
|
87
|
-
return basename(value) === value;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
function isTempRootOwnershipRecord(value: unknown): value is TempRootOwnershipRecord {
|
|
91
|
-
if (!isRecord(value)) return false;
|
|
92
|
-
if (value.kind !== TEMP_ROOT_MARKER_KIND || value.version !== TEMP_ROOT_MARKER_VERSION) return false;
|
|
93
|
-
if (!isPositiveFiniteNumber(value.createdAtMs)) return false;
|
|
94
|
-
if (value.leaseUpdatedAtMs !== undefined && !isPositiveFiniteNumber(value.leaseUpdatedAtMs)) return false;
|
|
95
|
-
if (value.ownerPid !== undefined) {
|
|
96
|
-
if (typeof value.ownerPid !== "number" || !Number.isSafeInteger(value.ownerPid) || value.ownerPid <= 0) return false;
|
|
97
|
-
}
|
|
98
|
-
if (value.ownerProcessStartIdentity !== undefined) {
|
|
99
|
-
if (typeof value.ownerProcessStartIdentity !== "string" || value.ownerProcessStartIdentity.trim() === "") return false;
|
|
100
|
-
}
|
|
101
|
-
if (value.ownerUid !== undefined) {
|
|
102
|
-
if (typeof value.ownerUid !== "number" || !Number.isSafeInteger(value.ownerUid) || value.ownerUid < 0) return false;
|
|
103
|
-
}
|
|
104
|
-
if (value.protectedChildNames !== undefined) {
|
|
105
|
-
if (!Array.isArray(value.protectedChildNames)) return false;
|
|
106
|
-
if (!value.protectedChildNames.every(isProtectedTempChildName)) return false;
|
|
107
|
-
}
|
|
108
|
-
return true;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
function getTempArtifactByteLength(content: string | Uint8Array): number {
|
|
112
|
-
return typeof content === "string" ? Buffer.byteLength(content) : content.byteLength;
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
function enqueueTempMutation<T>(task: () => Promise<T>): Promise<T> {
|
|
116
|
-
const nextTask = tempMutationQueue.then(task, task);
|
|
117
|
-
tempMutationQueue = nextTask.then(
|
|
118
|
-
() => undefined,
|
|
119
|
-
() => undefined,
|
|
120
|
-
);
|
|
121
|
-
return nextTask;
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
async function listArtifactFiles(directory: string, excludedNames: ReadonlySet<string> = new Set()): Promise<Array<{ mtimeMs: number; path: string; size: number }>> {
|
|
125
|
-
const entries = await readdir(directory, { withFileTypes: true }).catch(() => []);
|
|
126
|
-
const files: Array<{ mtimeMs: number; path: string; size: number }> = [];
|
|
127
|
-
for (const entry of entries) {
|
|
128
|
-
if (!entry.isFile() || excludedNames.has(entry.name)) continue;
|
|
129
|
-
const path = join(directory, entry.name);
|
|
130
|
-
const stats = await stat(path).catch(() => undefined);
|
|
131
|
-
if (stats?.isFile()) {
|
|
132
|
-
files.push({ mtimeMs: stats.mtimeMs, path, size: stats.size });
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
return files;
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
async function getTempRootArtifactBytes(tempRoot: string): Promise<number> {
|
|
139
|
-
const files = await listArtifactFiles(tempRoot, new Set([TEMP_ROOT_MARKER_FILE_NAME]));
|
|
140
|
-
return files.reduce((totalBytes, file) => totalBytes + file.size, 0);
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
async function readTempRootOwnershipMarker(tempRoot: string): Promise<TempRootOwnershipRecord | undefined> {
|
|
144
|
-
try {
|
|
145
|
-
const markerText = await readFile(join(tempRoot, TEMP_ROOT_MARKER_FILE_NAME), "utf8");
|
|
146
|
-
const parsed = JSON.parse(markerText) as unknown;
|
|
147
|
-
return isTempRootOwnershipRecord(parsed) ? parsed : undefined;
|
|
148
|
-
} catch {
|
|
149
|
-
return undefined;
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
function getProtectedTempChildName(tempRoot: string, childPath: string): string | undefined {
|
|
154
|
-
const normalizedTempRoot = resolve(tempRoot);
|
|
155
|
-
const normalizedChildPath = resolve(childPath);
|
|
156
|
-
if (dirname(normalizedChildPath) !== normalizedTempRoot) return undefined;
|
|
157
|
-
const childName = basename(normalizedChildPath);
|
|
158
|
-
return isProtectedTempChildName(childName) ? childName : undefined;
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
function normalizeProtectedChildNames(names: Iterable<string>): string[] {
|
|
162
|
-
return [...new Set([...names].filter(isProtectedTempChildName))].sort();
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
function getPersistedProtectedChildPaths(tempRoot: string, ownershipMarker: TempRootOwnershipRecord | undefined): Set<string> {
|
|
166
|
-
const normalizedTempRoot = resolve(tempRoot);
|
|
167
|
-
return new Set((ownershipMarker?.protectedChildNames ?? []).map((childName) => resolve(join(normalizedTempRoot, childName))));
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
async function writeTempRootOwnershipMarkerRecord(
|
|
171
|
-
tempRoot: string,
|
|
172
|
-
markerRecord: TempRootOwnershipRecord,
|
|
173
|
-
options: { flag?: "wx" } = {},
|
|
174
|
-
): Promise<string> {
|
|
175
|
-
const markerPath = join(tempRoot, TEMP_ROOT_MARKER_FILE_NAME);
|
|
176
|
-
await writeFile(markerPath, JSON.stringify(markerRecord, null, 2), {
|
|
177
|
-
encoding: "utf8",
|
|
178
|
-
flag: options.flag,
|
|
179
|
-
mode: 0o600,
|
|
180
|
-
});
|
|
181
|
-
await chmod(markerPath, 0o600).catch(() => undefined);
|
|
182
|
-
return markerPath;
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
async function persistProtectedTempChildren(tempRoot: string, protectedChildren: ReadonlySet<string>): Promise<void> {
|
|
186
|
-
if (protectedChildren.size === 0) return;
|
|
187
|
-
const ownershipMarker = await readTempRootOwnershipMarker(tempRoot);
|
|
188
|
-
if (!ownershipMarker) return;
|
|
189
|
-
const childNames = normalizeProtectedChildNames([
|
|
190
|
-
...(ownershipMarker.protectedChildNames ?? []),
|
|
191
|
-
...[...protectedChildren]
|
|
192
|
-
.map((path) => getProtectedTempChildName(tempRoot, path))
|
|
193
|
-
.filter((childName): childName is string => childName !== undefined),
|
|
194
|
-
]);
|
|
195
|
-
if (childNames.length === 0) return;
|
|
196
|
-
await writeTempRootOwnershipMarkerRecord(tempRoot, {
|
|
197
|
-
...ownershipMarker,
|
|
198
|
-
leaseUpdatedAtMs: Date.now(),
|
|
199
|
-
protectedChildNames: childNames,
|
|
200
|
-
});
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
async function getExistingProtectedChildren(
|
|
204
|
-
tempRoot: string,
|
|
205
|
-
protectedChildren: ReadonlySet<string>,
|
|
206
|
-
): Promise<Set<string>> {
|
|
207
|
-
const normalizedTempRoot = resolve(tempRoot);
|
|
208
|
-
const existingChildren = new Set<string>();
|
|
209
|
-
for (const path of protectedChildren) {
|
|
210
|
-
const normalizedPath = resolve(path);
|
|
211
|
-
if (dirname(normalizedPath) !== normalizedTempRoot) continue;
|
|
212
|
-
if (await stat(normalizedPath).then((stats) => stats.isDirectory(), () => false)) {
|
|
213
|
-
existingChildren.add(normalizedPath);
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
return existingChildren;
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
async function removeTempRootChildrenExcept(tempRoot: string, protectedChildren: ReadonlySet<string>): Promise<void> {
|
|
220
|
-
const entries = await readdir(tempRoot, { withFileTypes: true }).catch(() => []);
|
|
221
|
-
await Promise.all(entries.map(async (entry) => {
|
|
222
|
-
if (entry.name === TEMP_ROOT_MARKER_FILE_NAME) return;
|
|
223
|
-
const entryPath = join(tempRoot, entry.name);
|
|
224
|
-
if (protectedChildren.has(resolve(entryPath))) return;
|
|
225
|
-
await rm(entryPath, { force: true, recursive: true }).catch(() => undefined);
|
|
226
|
-
}));
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
async function getProcessStartIdentity(pid: number | undefined): Promise<string | undefined> {
|
|
230
|
-
if (pid === undefined) return undefined;
|
|
231
|
-
if (!Number.isSafeInteger(pid) || pid <= 0) return undefined;
|
|
232
|
-
try {
|
|
233
|
-
const { stdout } = await execFileAsync("ps", ["-p", String(pid), "-o", "lstart="], {
|
|
234
|
-
timeout: PROCESS_START_IDENTITY_TIMEOUT_MS,
|
|
235
|
-
});
|
|
236
|
-
const identity = stdout.trim().replace(/\s+/g, " ");
|
|
237
|
-
return identity || undefined;
|
|
238
|
-
} catch {
|
|
239
|
-
return undefined;
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
export async function writeSecureTempRootOwnershipMarker(
|
|
244
|
-
tempRoot: string,
|
|
245
|
-
options: TempRootOwnershipMarkerOptions = {},
|
|
246
|
-
): Promise<string> {
|
|
247
|
-
const createdAtMs = options.createdAtMs ?? Date.now();
|
|
248
|
-
const ownerPid = options.ownerPid ?? process.pid;
|
|
249
|
-
const markerRecord: TempRootOwnershipRecord = {
|
|
250
|
-
createdAtMs,
|
|
251
|
-
kind: TEMP_ROOT_MARKER_KIND,
|
|
252
|
-
leaseUpdatedAtMs: options.leaseUpdatedAtMs ?? createdAtMs,
|
|
253
|
-
ownerPid,
|
|
254
|
-
ownerProcessStartIdentity: options.ownerProcessStartIdentity ?? (await getProcessStartIdentity(ownerPid)),
|
|
255
|
-
ownerUid: getCurrentProcessUid(),
|
|
256
|
-
version: TEMP_ROOT_MARKER_VERSION,
|
|
257
|
-
};
|
|
258
|
-
return await writeTempRootOwnershipMarkerRecord(tempRoot, markerRecord, { flag: "wx" });
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
async function refreshSecureTempRootLease(tempRoot: string): Promise<void> {
|
|
262
|
-
const ownershipMarker = await readTempRootOwnershipMarker(tempRoot);
|
|
263
|
-
if (!ownershipMarker) return;
|
|
264
|
-
if (ownershipMarker.ownerPid !== process.pid) return;
|
|
265
|
-
const currentUid = getCurrentProcessUid();
|
|
266
|
-
if (currentUid !== undefined && ownershipMarker.ownerUid !== undefined && ownershipMarker.ownerUid !== currentUid) return;
|
|
267
|
-
const currentProcessStartIdentity = await getProcessStartIdentity(process.pid);
|
|
268
|
-
if (
|
|
269
|
-
ownershipMarker.ownerProcessStartIdentity !== undefined &&
|
|
270
|
-
currentProcessStartIdentity !== undefined &&
|
|
271
|
-
ownershipMarker.ownerProcessStartIdentity !== currentProcessStartIdentity
|
|
272
|
-
) {
|
|
273
|
-
return;
|
|
274
|
-
}
|
|
275
|
-
const refreshedMarker: TempRootOwnershipRecord = {
|
|
276
|
-
...ownershipMarker,
|
|
277
|
-
leaseUpdatedAtMs: Date.now(),
|
|
278
|
-
ownerPid: process.pid,
|
|
279
|
-
ownerProcessStartIdentity: currentProcessStartIdentity ?? ownershipMarker.ownerProcessStartIdentity,
|
|
280
|
-
ownerUid: currentUid,
|
|
281
|
-
};
|
|
282
|
-
await writeTempRootOwnershipMarkerRecord(tempRoot, refreshedMarker);
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
async function getMarkerOwnerLiveness(ownershipMarker: TempRootOwnershipRecord): Promise<ProcessLiveness> {
|
|
286
|
-
const pid = ownershipMarker.ownerPid;
|
|
287
|
-
if (pid === undefined) return "unknown";
|
|
288
|
-
try {
|
|
289
|
-
process.kill(pid, 0);
|
|
290
|
-
} catch (error) {
|
|
291
|
-
const errorWithCode = error as NodeJS.ErrnoException;
|
|
292
|
-
if (errorWithCode.code === "ESRCH") return "dead";
|
|
293
|
-
if (errorWithCode.code !== "EPERM") return "unknown";
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
const currentProcessStartIdentity = await getProcessStartIdentity(pid);
|
|
297
|
-
if (ownershipMarker.ownerProcessStartIdentity === undefined || currentProcessStartIdentity === undefined) {
|
|
298
|
-
return "unknown";
|
|
299
|
-
}
|
|
300
|
-
return ownershipMarker.ownerProcessStartIdentity === currentProcessStartIdentity ? "alive" : "dead";
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
async function pruneStaleTempRoots(currentTempRoot: string | undefined): Promise<void> {
|
|
304
|
-
const entries = await readdir(tmpdir(), { withFileTypes: true }).catch(() => []);
|
|
305
|
-
const cutoffTime = Date.now() - STALE_TEMP_ROOT_MAX_AGE_MS;
|
|
306
|
-
const currentUid = getCurrentProcessUid();
|
|
307
|
-
|
|
308
|
-
await Promise.all(
|
|
309
|
-
entries
|
|
310
|
-
.filter((entry) => entry.isDirectory() && entry.name.startsWith(TEMP_ROOT_PREFIX))
|
|
311
|
-
.map(async (entry) => {
|
|
312
|
-
const path = join(tmpdir(), entry.name);
|
|
313
|
-
if (path === currentTempRoot) return;
|
|
314
|
-
|
|
315
|
-
const ownershipMarker = await readTempRootOwnershipMarker(path);
|
|
316
|
-
if (!ownershipMarker) return;
|
|
317
|
-
if (
|
|
318
|
-
currentUid !== undefined &&
|
|
319
|
-
ownershipMarker.ownerUid !== undefined &&
|
|
320
|
-
ownershipMarker.ownerUid !== currentUid
|
|
321
|
-
) {
|
|
322
|
-
return;
|
|
323
|
-
}
|
|
324
|
-
const staleTimestampMs = ownershipMarker.leaseUpdatedAtMs ?? ownershipMarker.createdAtMs;
|
|
325
|
-
if (staleTimestampMs >= cutoffTime) return;
|
|
326
|
-
// Preserve roots when owner liveness cannot be proven; safe cleanup beats deleting another live process's files.
|
|
327
|
-
if ((await getMarkerOwnerLiveness(ownershipMarker)) !== "dead") return;
|
|
328
|
-
|
|
329
|
-
const stats = await stat(path).catch(() => undefined);
|
|
330
|
-
if (!stats?.isDirectory()) return;
|
|
331
|
-
const protectedChildren = await getExistingProtectedChildren(
|
|
332
|
-
path,
|
|
333
|
-
getPersistedProtectedChildPaths(path, ownershipMarker),
|
|
334
|
-
);
|
|
335
|
-
if (protectedChildren.size > 0) {
|
|
336
|
-
await removeTempRootChildrenExcept(path, protectedChildren);
|
|
337
|
-
return;
|
|
338
|
-
}
|
|
339
|
-
await rm(path, { force: true, recursive: true }).catch(() => undefined);
|
|
340
|
-
}),
|
|
341
|
-
);
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
function getProtectedChildrenForRoot(tempRoot: string): Set<string> {
|
|
345
|
-
const normalizedTempRoot = resolve(tempRoot);
|
|
346
|
-
return new Set(
|
|
347
|
-
[...protectedTempChildren].filter((path) => dirname(path) === normalizedTempRoot && existsSync(path)),
|
|
348
|
-
);
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
function removeTempRootChildrenExceptSync(tempRoot: string, protectedChildren: ReadonlySet<string>): void {
|
|
352
|
-
for (const entry of readdirSync(tempRoot, { withFileTypes: true })) {
|
|
353
|
-
if (entry.name === TEMP_ROOT_MARKER_FILE_NAME) continue;
|
|
354
|
-
const entryPath = join(tempRoot, entry.name);
|
|
355
|
-
if (protectedChildren.has(resolve(entryPath))) continue;
|
|
356
|
-
rmSync(entryPath, { force: true, recursive: true });
|
|
357
|
-
}
|
|
358
|
-
}
|
|
359
|
-
|
|
360
|
-
function registerExitCleanup(): void {
|
|
361
|
-
if (exitCleanupRegistered) return;
|
|
362
|
-
exitCleanupRegistered = true;
|
|
363
|
-
process.once("exit", () => {
|
|
364
|
-
for (const tempRoot of ownedTempRoots) {
|
|
365
|
-
try {
|
|
366
|
-
const protectedChildren = getProtectedChildrenForRoot(tempRoot);
|
|
367
|
-
if (protectedChildren.size === 0) {
|
|
368
|
-
rmSync(tempRoot, { force: true, recursive: true });
|
|
369
|
-
} else {
|
|
370
|
-
removeTempRootChildrenExceptSync(tempRoot, protectedChildren);
|
|
371
|
-
}
|
|
372
|
-
} catch {
|
|
373
|
-
// Best-effort cleanup only.
|
|
374
|
-
}
|
|
375
|
-
}
|
|
376
|
-
});
|
|
377
|
-
}
|
|
378
|
-
|
|
379
|
-
export function getSecureTempRootMaxBytes(env: NodeJS.ProcessEnv = process.env): number {
|
|
380
|
-
return parsePositiveInteger(env[TEMP_ROOT_MAX_BYTES_ENV]) ?? DEFAULT_TEMP_ROOT_MAX_BYTES;
|
|
381
|
-
}
|
|
382
|
-
|
|
383
|
-
export function getPersistentSessionArtifactMaxBytes(env: NodeJS.ProcessEnv = process.env): number {
|
|
384
|
-
return parsePositiveInteger(env[SESSION_ARTIFACT_MAX_BYTES_ENV]) ?? DEFAULT_SESSION_ARTIFACT_MAX_BYTES;
|
|
385
|
-
}
|
|
386
|
-
|
|
387
|
-
async function assertSecureTempRootBudget(tempRoot: string, additionalBytes: number): Promise<void> {
|
|
388
|
-
if (additionalBytes <= 0) return;
|
|
389
|
-
const currentBytes = await getTempRootArtifactBytes(tempRoot);
|
|
390
|
-
const maxBytes = getSecureTempRootMaxBytes();
|
|
391
|
-
const nextBytes = currentBytes + additionalBytes;
|
|
392
|
-
if (nextBytes > maxBytes) {
|
|
393
|
-
throw new Error(`pi-agent-browser temp spill budget exceeded (${nextBytes} bytes > ${maxBytes} byte limit).`);
|
|
394
|
-
}
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
export async function cleanupSecureTempArtifacts(options: { preservePaths?: readonly string[] } = {}): Promise<void> {
|
|
398
|
-
await enqueueTempMutation(async () => {
|
|
399
|
-
const tempRoot = await sessionTempRootPromise?.catch(() => undefined);
|
|
400
|
-
if (!tempRoot) return;
|
|
401
|
-
const normalizedTempRoot = resolve(tempRoot);
|
|
402
|
-
for (const path of options.preservePaths ?? []) {
|
|
403
|
-
const childName = getProtectedTempChildName(normalizedTempRoot, path);
|
|
404
|
-
if (childName) protectedTempChildren.add(resolve(join(normalizedTempRoot, childName)));
|
|
405
|
-
}
|
|
406
|
-
const preservedChildren = await getExistingProtectedChildren(normalizedTempRoot, protectedTempChildren);
|
|
407
|
-
for (const path of protectedTempChildren) {
|
|
408
|
-
if (dirname(path) === normalizedTempRoot && !preservedChildren.has(path)) protectedTempChildren.delete(path);
|
|
409
|
-
}
|
|
410
|
-
if (preservedChildren.size === 0) {
|
|
411
|
-
sessionTempRootPromise = undefined;
|
|
412
|
-
ownedTempRoots.delete(tempRoot);
|
|
413
|
-
await rm(tempRoot, { force: true, recursive: true }).catch(() => undefined);
|
|
414
|
-
return;
|
|
415
|
-
}
|
|
416
|
-
await persistProtectedTempChildren(tempRoot, preservedChildren);
|
|
417
|
-
await removeTempRootChildrenExcept(tempRoot, preservedChildren);
|
|
418
|
-
await refreshSecureTempRootLease(tempRoot).catch(() => undefined);
|
|
419
|
-
});
|
|
420
|
-
}
|
|
421
|
-
|
|
422
|
-
async function ensurePersistentSessionArtifactDir(store: PersistentSessionArtifactStore): Promise<string> {
|
|
423
|
-
const rootDir = join(store.sessionDir, SESSION_ARTIFACTS_ROOT_DIR_NAME);
|
|
424
|
-
const sessionDir = join(rootDir, store.sessionId);
|
|
425
|
-
await mkdir(rootDir, { recursive: true, mode: 0o700 });
|
|
426
|
-
await chmod(rootDir, 0o700).catch(() => undefined);
|
|
427
|
-
await mkdir(sessionDir, { recursive: true, mode: 0o700 });
|
|
428
|
-
await chmod(sessionDir, 0o700).catch(() => undefined);
|
|
429
|
-
return sessionDir;
|
|
430
|
-
}
|
|
431
|
-
|
|
432
|
-
async function prunePersistentSessionArtifactsToBudget(
|
|
433
|
-
sessionArtifactDir: string,
|
|
434
|
-
additionalBytes: number,
|
|
435
|
-
protectedPaths: ReadonlySet<string>,
|
|
436
|
-
): Promise<PersistentSessionArtifactEviction[]> {
|
|
437
|
-
if (additionalBytes <= 0) return [];
|
|
438
|
-
const maxBytes = getPersistentSessionArtifactMaxBytes();
|
|
439
|
-
let files = await listArtifactFiles(sessionArtifactDir);
|
|
440
|
-
let totalBytes = files.reduce((total, file) => total + file.size, 0);
|
|
441
|
-
if (totalBytes + additionalBytes <= maxBytes) {
|
|
442
|
-
return [];
|
|
443
|
-
}
|
|
444
|
-
const evictedArtifacts: PersistentSessionArtifactEviction[] = [];
|
|
445
|
-
files = files.sort((left, right) => left.mtimeMs - right.mtimeMs || left.path.localeCompare(right.path));
|
|
446
|
-
for (const file of files) {
|
|
447
|
-
if (protectedPaths.has(file.path)) {
|
|
448
|
-
continue;
|
|
449
|
-
}
|
|
450
|
-
await rm(file.path, { force: true }).catch(() => undefined);
|
|
451
|
-
evictedArtifacts.push({ mtimeMs: file.mtimeMs, path: file.path, sizeBytes: file.size });
|
|
452
|
-
totalBytes -= file.size;
|
|
453
|
-
if (totalBytes + additionalBytes <= maxBytes) {
|
|
454
|
-
return evictedArtifacts;
|
|
455
|
-
}
|
|
456
|
-
}
|
|
457
|
-
throw new Error(`pi-agent-browser persisted spill budget exceeded (${totalBytes + additionalBytes} bytes > ${maxBytes} byte limit).`);
|
|
458
|
-
}
|
|
459
|
-
|
|
460
|
-
async function getSessionTempRoot(): Promise<string> {
|
|
461
|
-
if (!sessionTempRootPromise) {
|
|
462
|
-
sessionTempRootPromise = (async () => {
|
|
463
|
-
await pruneStaleTempRoots(undefined);
|
|
464
|
-
const tempRoot = await mkdtemp(join(tmpdir(), TEMP_ROOT_PREFIX));
|
|
465
|
-
await chmod(tempRoot, 0o700).catch(() => undefined);
|
|
466
|
-
await writeSecureTempRootOwnershipMarker(tempRoot);
|
|
467
|
-
ownedTempRoots.add(tempRoot);
|
|
468
|
-
registerExitCleanup();
|
|
469
|
-
return tempRoot;
|
|
470
|
-
})();
|
|
471
|
-
}
|
|
472
|
-
|
|
473
|
-
const tempRoot = await sessionTempRootPromise;
|
|
474
|
-
await refreshSecureTempRootLease(tempRoot).catch(() => undefined);
|
|
475
|
-
await pruneStaleTempRoots(tempRoot).catch(() => undefined);
|
|
476
|
-
return tempRoot;
|
|
477
|
-
}
|
|
478
|
-
|
|
479
|
-
export async function openSecureTempFile(prefix: string, suffix: string): Promise<{ fileHandle: Awaited<ReturnType<typeof open>>; path: string }> {
|
|
480
|
-
const tempRoot = await getSessionTempRoot();
|
|
481
|
-
const path = join(tempRoot, `${prefix}-${randomBytes(8).toString("hex")}${suffix}`);
|
|
482
|
-
const fileHandle = await open(path, "wx", 0o600);
|
|
483
|
-
return { fileHandle, path };
|
|
484
|
-
}
|
|
485
|
-
|
|
486
|
-
export async function writeSecureTempChunk(options: {
|
|
487
|
-
content: string | Uint8Array;
|
|
488
|
-
fileHandle: Awaited<ReturnType<typeof open>>;
|
|
489
|
-
path: string;
|
|
490
|
-
}): Promise<void> {
|
|
491
|
-
const { content, fileHandle, path } = options;
|
|
492
|
-
await enqueueTempMutation(async () => {
|
|
493
|
-
const tempRoot = dirname(path);
|
|
494
|
-
await refreshSecureTempRootLease(tempRoot).catch(() => undefined);
|
|
495
|
-
await assertSecureTempRootBudget(tempRoot, getTempArtifactByteLength(content));
|
|
496
|
-
await fileHandle.appendFile(content);
|
|
497
|
-
});
|
|
498
|
-
}
|
|
499
|
-
|
|
500
|
-
export async function writeSecureTempFile(options: {
|
|
501
|
-
content: string | Uint8Array;
|
|
502
|
-
prefix: string;
|
|
503
|
-
suffix: string;
|
|
504
|
-
}): Promise<string> {
|
|
505
|
-
const { content, prefix, suffix } = options;
|
|
506
|
-
const { fileHandle, path } = await openSecureTempFile(prefix, suffix);
|
|
507
|
-
try {
|
|
508
|
-
await writeSecureTempChunk({ content, fileHandle, path });
|
|
509
|
-
} catch (error) {
|
|
510
|
-
await rm(path, { force: true }).catch(() => undefined);
|
|
511
|
-
throw error;
|
|
512
|
-
} finally {
|
|
513
|
-
await fileHandle.close();
|
|
514
|
-
}
|
|
515
|
-
return path;
|
|
516
|
-
}
|
|
517
|
-
|
|
518
|
-
export async function createSecureTempDirectory(prefix: string): Promise<string> {
|
|
519
|
-
const tempRoot = await getSessionTempRoot();
|
|
520
|
-
await assertSecureTempRootBudget(tempRoot, 0);
|
|
521
|
-
const directory = await mkdtemp(join(tempRoot, prefix));
|
|
522
|
-
await chmod(directory, 0o700).catch(() => undefined);
|
|
523
|
-
await refreshSecureTempRootLease(tempRoot).catch(() => undefined);
|
|
524
|
-
return directory;
|
|
525
|
-
}
|
|
526
|
-
|
|
527
|
-
export async function getSecureTempChildDirectoryValidationError(path: string, childPrefix: string): Promise<string | undefined> {
|
|
528
|
-
const parentDirectory = dirname(path);
|
|
529
|
-
const childName = path.slice(parentDirectory.length + 1);
|
|
530
|
-
if (!childName.startsWith(childPrefix)) {
|
|
531
|
-
return `Refusing to remove ${path}; expected wrapper temp child prefix ${childPrefix}.`;
|
|
532
|
-
}
|
|
533
|
-
const ownershipMarker = await readTempRootOwnershipMarker(parentDirectory);
|
|
534
|
-
if (!ownershipMarker) {
|
|
535
|
-
return `Refusing to remove ${path}; parent directory is not a pi-agent-browser owned temp root.`;
|
|
536
|
-
}
|
|
537
|
-
const currentUid = getCurrentProcessUid();
|
|
538
|
-
if (currentUid !== undefined && ownershipMarker.ownerUid !== undefined && ownershipMarker.ownerUid !== currentUid) {
|
|
539
|
-
return `Refusing to remove ${path}; parent temp root is owned by uid ${ownershipMarker.ownerUid}, not current uid ${currentUid}.`;
|
|
540
|
-
}
|
|
541
|
-
return undefined;
|
|
542
|
-
}
|
|
543
|
-
|
|
544
|
-
export async function writePersistentSessionArtifactFile(options: {
|
|
545
|
-
content: string | Uint8Array;
|
|
546
|
-
prefix: string;
|
|
547
|
-
store: PersistentSessionArtifactStore;
|
|
548
|
-
suffix: string;
|
|
549
|
-
}): Promise<PersistentSessionArtifactWriteResult> {
|
|
550
|
-
const { content, prefix, store, suffix } = options;
|
|
551
|
-
return await enqueueTempMutation(async () => {
|
|
552
|
-
const artifactDir = await ensurePersistentSessionArtifactDir(store);
|
|
553
|
-
const evictedArtifacts = await prunePersistentSessionArtifactsToBudget(
|
|
554
|
-
artifactDir,
|
|
555
|
-
getTempArtifactByteLength(content),
|
|
556
|
-
new Set((store.protectedPaths ?? []).filter((path) => dirname(path) === artifactDir)),
|
|
557
|
-
);
|
|
558
|
-
const path = join(artifactDir, `${prefix}-${randomBytes(8).toString("hex")}${suffix}`);
|
|
559
|
-
const fileHandle = await open(path, "wx", 0o600);
|
|
560
|
-
try {
|
|
561
|
-
await fileHandle.writeFile(content);
|
|
562
|
-
} catch (error) {
|
|
563
|
-
await rm(path, { force: true }).catch(() => undefined);
|
|
564
|
-
throw error;
|
|
565
|
-
} finally {
|
|
566
|
-
await fileHandle.close().catch(() => undefined);
|
|
567
|
-
}
|
|
568
|
-
return { evictedArtifacts, path };
|
|
569
|
-
});
|
|
570
|
-
}
|
|
571
|
-
|
|
572
|
-
export async function getSecureTempDebugState(): Promise<{ currentTempRoot?: string; ownedTempRoots: string[] }> {
|
|
573
|
-
return {
|
|
574
|
-
currentTempRoot: await sessionTempRootPromise?.catch(() => undefined),
|
|
575
|
-
ownedTempRoots: [...ownedTempRoots].sort(),
|
|
576
|
-
};
|
|
577
|
-
}
|