negotium 0.1.21 → 0.1.22
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/agent-helpers.js +183 -200
- package/dist/agent-helpers.js.map +8 -8
- package/dist/cron.js +231 -220
- package/dist/cron.js.map +10 -10
- package/dist/hosted-agent.js +233 -250
- package/dist/hosted-agent.js.map +9 -9
- package/dist/main.js +391 -395
- package/dist/main.js.map +12 -12
- package/dist/runtime/scripts/browser-webauthn-policy.mjs +37 -0
- package/dist/runtime/scripts/mcp-patchright-http.mjs +8 -3
- package/dist/runtime/src/agents/vault-tool-policy.ts +13 -4
- package/dist/runtime/src/platform/mcp-config.ts +2 -1
- package/dist/runtime/src/platform/playwright/manager.ts +18 -0
- package/dist/runtime/src/prompts/sessions/channel-system.md +1 -1
- package/dist/runtime/src/prompts/sessions/topic-system.md +1 -1
- package/dist/runtime/src/version.ts +1 -1
- package/dist/types/packages/core/src/agents/vault-tool-policy.d.ts +1 -1
- package/dist/types/packages/core/src/version.d.ts +1 -1
- package/package.json +1 -1
package/dist/main.js
CHANGED
|
@@ -1958,219 +1958,6 @@ var init_sensitive_path = __esm(() => {
|
|
|
1958
1958
|
];
|
|
1959
1959
|
});
|
|
1960
1960
|
|
|
1961
|
-
// ../../packages/core/src/storage/vault-crypto.ts
|
|
1962
|
-
import { createCipheriv, createDecipheriv, createHash, randomBytes as randomBytes3 } from "crypto";
|
|
1963
|
-
function encryptionKey(masterKey = VAULT_MASTER_KEY) {
|
|
1964
|
-
return createHash("sha256").update("otium-vault-value-v1\x00", "utf8").update(masterKey, "utf8").digest().subarray(0, KEY_BYTES);
|
|
1965
|
-
}
|
|
1966
|
-
function aad(userId, key) {
|
|
1967
|
-
return Buffer.from(`${userId}\x00${key.toUpperCase()}`, "utf8");
|
|
1968
|
-
}
|
|
1969
|
-
function isEncryptedVaultValue(value) {
|
|
1970
|
-
return value.startsWith(ENVELOPE_PREFIX);
|
|
1971
|
-
}
|
|
1972
|
-
function encryptVaultValue(userId, key, value, masterKey = VAULT_MASTER_KEY) {
|
|
1973
|
-
const iv = randomBytes3(IV_BYTES);
|
|
1974
|
-
const cipher = createCipheriv("aes-256-gcm", encryptionKey(masterKey), iv);
|
|
1975
|
-
cipher.setAAD(aad(userId, key));
|
|
1976
|
-
const ciphertext = Buffer.concat([cipher.update(value, "utf8"), cipher.final()]);
|
|
1977
|
-
const tag = cipher.getAuthTag();
|
|
1978
|
-
return `${ENVELOPE_PREFIX}${iv.toString("base64url")}.${ciphertext.toString("base64url")}.${tag.toString("base64url")}`;
|
|
1979
|
-
}
|
|
1980
|
-
function decryptVaultValue(userId, key, storedValue, masterKey = VAULT_MASTER_KEY) {
|
|
1981
|
-
if (!isEncryptedVaultValue(storedValue)) {
|
|
1982
|
-
return { value: storedValue, legacyPlaintext: true };
|
|
1983
|
-
}
|
|
1984
|
-
const encoded = storedValue.slice(ENVELOPE_PREFIX.length);
|
|
1985
|
-
const [ivPart, ciphertextPart, tagPart, ...extra] = encoded.split(".");
|
|
1986
|
-
if (!ivPart || ciphertextPart === undefined || !tagPart || extra.length > 0) {
|
|
1987
|
-
throw new Error("Invalid encrypted vault value");
|
|
1988
|
-
}
|
|
1989
|
-
const iv = Buffer.from(ivPart, "base64url");
|
|
1990
|
-
const ciphertext = Buffer.from(ciphertextPart, "base64url");
|
|
1991
|
-
const tag = Buffer.from(tagPart, "base64url");
|
|
1992
|
-
if (iv.length !== IV_BYTES || tag.length !== 16) {
|
|
1993
|
-
throw new Error("Invalid encrypted vault value");
|
|
1994
|
-
}
|
|
1995
|
-
const decipher = createDecipheriv("aes-256-gcm", encryptionKey(masterKey), iv);
|
|
1996
|
-
decipher.setAAD(aad(userId, key));
|
|
1997
|
-
decipher.setAuthTag(tag);
|
|
1998
|
-
const plaintext = Buffer.concat([decipher.update(ciphertext), decipher.final()]);
|
|
1999
|
-
return { value: plaintext.toString("utf8"), legacyPlaintext: false };
|
|
2000
|
-
}
|
|
2001
|
-
var ENVELOPE_PREFIX = "otium-vault:v1:", IV_BYTES = 12, KEY_BYTES = 32;
|
|
2002
|
-
var init_vault_crypto = __esm(() => {
|
|
2003
|
-
init_config();
|
|
2004
|
-
});
|
|
2005
|
-
|
|
2006
|
-
// ../../packages/core/src/storage/vault.ts
|
|
2007
|
-
import { chmodSync as chmodSync2, mkdirSync as mkdirSync7 } from "fs";
|
|
2008
|
-
import { join as join10 } from "path";
|
|
2009
|
-
function initializeVaultDatabase(database) {
|
|
2010
|
-
database.exec("PRAGMA journal_mode = WAL");
|
|
2011
|
-
database.exec("PRAGMA busy_timeout = 5000");
|
|
2012
|
-
database.exec(`
|
|
2013
|
-
CREATE TABLE IF NOT EXISTS vault (
|
|
2014
|
-
user_id TEXT NOT NULL,
|
|
2015
|
-
key TEXT NOT NULL,
|
|
2016
|
-
value TEXT NOT NULL,
|
|
2017
|
-
description TEXT NOT NULL DEFAULT '',
|
|
2018
|
-
PRIMARY KEY (user_id, key)
|
|
2019
|
-
)
|
|
2020
|
-
`);
|
|
2021
|
-
{
|
|
2022
|
-
const cols = database.prepare("PRAGMA table_info(vault)").all();
|
|
2023
|
-
const uid = cols.find((c) => c.name === "user_id");
|
|
2024
|
-
if (uid && uid.type.toUpperCase() === "INTEGER") {
|
|
2025
|
-
database.exec("BEGIN");
|
|
2026
|
-
database.exec(`
|
|
2027
|
-
CREATE TABLE vault_migrated (
|
|
2028
|
-
user_id TEXT NOT NULL,
|
|
2029
|
-
key TEXT NOT NULL,
|
|
2030
|
-
value TEXT NOT NULL,
|
|
2031
|
-
description TEXT NOT NULL DEFAULT '',
|
|
2032
|
-
PRIMARY KEY (user_id, key)
|
|
2033
|
-
)
|
|
2034
|
-
`);
|
|
2035
|
-
database.exec("INSERT INTO vault_migrated SELECT CAST(user_id AS TEXT), key, value, description FROM vault");
|
|
2036
|
-
database.exec("DROP TABLE vault");
|
|
2037
|
-
database.exec("ALTER TABLE vault_migrated RENAME TO vault");
|
|
2038
|
-
database.exec("COMMIT");
|
|
2039
|
-
}
|
|
2040
|
-
}
|
|
2041
|
-
}
|
|
2042
|
-
function openVaultDatabase(dataDir) {
|
|
2043
|
-
const path = join10(dataDir, "vault.db");
|
|
2044
|
-
mkdirSync7(dataDir, { recursive: true });
|
|
2045
|
-
const database = new Database(path, { create: true });
|
|
2046
|
-
chmodSync2(path, 384);
|
|
2047
|
-
initializeVaultDatabase(database);
|
|
2048
|
-
return database;
|
|
2049
|
-
}
|
|
2050
|
-
function activeVaultDatabase() {
|
|
2051
|
-
if (!vaultDb)
|
|
2052
|
-
vaultDb = openVaultDatabase(DATA_DIR);
|
|
2053
|
-
return vaultDb;
|
|
2054
|
-
}
|
|
2055
|
-
function normalizeVaultKey(key) {
|
|
2056
|
-
return key.trim().toUpperCase();
|
|
2057
|
-
}
|
|
2058
|
-
function validateVaultKey(key) {
|
|
2059
|
-
return VAULT_KEY_PATTERN.test(normalizeVaultKey(key));
|
|
2060
|
-
}
|
|
2061
|
-
function decryptRow(userId, key, storedValue) {
|
|
2062
|
-
const database = activeVaultDatabase();
|
|
2063
|
-
const decoded = decryptVaultValue(userId, key, storedValue, vaultMasterKey);
|
|
2064
|
-
if (decoded.legacyPlaintext) {
|
|
2065
|
-
database.prepare("UPDATE vault SET value = ? WHERE user_id = ? AND key = ? AND value = ?").run(encryptVaultValue(userId, key, decoded.value, vaultMasterKey), userId, key, storedValue);
|
|
2066
|
-
}
|
|
2067
|
-
return decoded.value;
|
|
2068
|
-
}
|
|
2069
|
-
function vaultListWithValues(userId) {
|
|
2070
|
-
const rows = activeVaultDatabase().prepare("SELECT key, description, value FROM vault WHERE user_id = ? ORDER BY key").all(userId);
|
|
2071
|
-
return rows.map((row) => ({
|
|
2072
|
-
key: row.key,
|
|
2073
|
-
description: row.description,
|
|
2074
|
-
value: decryptRow(userId, row.key, row.value)
|
|
2075
|
-
}));
|
|
2076
|
-
}
|
|
2077
|
-
function vaultSet(userId, key, value, description = "") {
|
|
2078
|
-
const normalizedKey = normalizeVaultKey(key);
|
|
2079
|
-
activeVaultDatabase().prepare(`INSERT INTO vault (user_id, key, value, description) VALUES (?, ?, ?, ?)
|
|
2080
|
-
ON CONFLICT (user_id, key) DO UPDATE
|
|
2081
|
-
SET value = excluded.value, description = excluded.description`).run(userId, normalizedKey, encryptVaultValue(userId, normalizedKey, value, vaultMasterKey), description);
|
|
2082
|
-
}
|
|
2083
|
-
function vaultHasKey(userId, key) {
|
|
2084
|
-
const row = activeVaultDatabase().prepare("SELECT 1 FROM vault WHERE user_id = ? AND key = ?").get(userId, normalizeVaultKey(key));
|
|
2085
|
-
return row != null;
|
|
2086
|
-
}
|
|
2087
|
-
function vaultDel(userId, key) {
|
|
2088
|
-
const result = activeVaultDatabase().prepare("DELETE FROM vault WHERE user_id = ? AND key = ?").run(userId, normalizeVaultKey(key));
|
|
2089
|
-
return result.changes > 0;
|
|
2090
|
-
}
|
|
2091
|
-
function vaultList(userId) {
|
|
2092
|
-
return activeVaultDatabase().prepare("SELECT key, description FROM vault WHERE user_id = ? ORDER BY key").all(userId);
|
|
2093
|
-
}
|
|
2094
|
-
function vaultSubstituteDetailed(userId, text) {
|
|
2095
|
-
const entries = new Map(vaultListWithValues(userId).map((entry) => [entry.key, entry.value]));
|
|
2096
|
-
const usedKeys = new Set;
|
|
2097
|
-
const substituted = text.replace(/\{\{([^}]+)\}\}/g, (match, rawKey) => {
|
|
2098
|
-
const key = normalizeVaultKey(rawKey);
|
|
2099
|
-
const value = entries.get(key);
|
|
2100
|
-
if (value === undefined)
|
|
2101
|
-
return match;
|
|
2102
|
-
usedKeys.add(key);
|
|
2103
|
-
return value;
|
|
2104
|
-
});
|
|
2105
|
-
return { text: substituted, usedKeys: [...usedKeys] };
|
|
2106
|
-
}
|
|
2107
|
-
function valueReferencesVaultKey(userId, value) {
|
|
2108
|
-
const keys = new Set(vaultList(userId).map((entry) => entry.key));
|
|
2109
|
-
const visit = (candidate) => {
|
|
2110
|
-
if (typeof candidate === "string") {
|
|
2111
|
-
for (const match of candidate.matchAll(/\{\{([^}]+)\}\}/g)) {
|
|
2112
|
-
if (keys.has(normalizeVaultKey(match[1] ?? "")))
|
|
2113
|
-
return true;
|
|
2114
|
-
}
|
|
2115
|
-
return false;
|
|
2116
|
-
}
|
|
2117
|
-
if (Array.isArray(candidate))
|
|
2118
|
-
return candidate.some(visit);
|
|
2119
|
-
if (candidate && typeof candidate === "object") {
|
|
2120
|
-
return Object.values(candidate).some(visit);
|
|
2121
|
-
}
|
|
2122
|
-
return false;
|
|
2123
|
-
};
|
|
2124
|
-
return visit(value);
|
|
2125
|
-
}
|
|
2126
|
-
function encodedSecretForms(value) {
|
|
2127
|
-
const forms = new Set([
|
|
2128
|
-
value,
|
|
2129
|
-
encodeURIComponent(value),
|
|
2130
|
-
Buffer.from(value, "utf8").toString("base64"),
|
|
2131
|
-
Buffer.from(value, "utf8").toString("base64url"),
|
|
2132
|
-
Buffer.from(value, "utf8").toString("hex")
|
|
2133
|
-
]);
|
|
2134
|
-
forms.delete("");
|
|
2135
|
-
return [...forms].sort((a, b) => b.length - a.length);
|
|
2136
|
-
}
|
|
2137
|
-
function redactVaultSecrets(userId, text) {
|
|
2138
|
-
const candidates = vaultListWithValues(userId).flatMap((entry) => encodedSecretForms(entry.value).map((form) => ({ form, key: entry.key }))).sort((a, b) => b.form.length - a.form.length || a.key.localeCompare(b.key));
|
|
2139
|
-
if (candidates.length === 0)
|
|
2140
|
-
return text;
|
|
2141
|
-
const candidatesByFirstCharacter = new Map;
|
|
2142
|
-
for (const candidate of candidates) {
|
|
2143
|
-
const first = candidate.form[0];
|
|
2144
|
-
if (!first)
|
|
2145
|
-
continue;
|
|
2146
|
-
const bucket = candidatesByFirstCharacter.get(first) ?? [];
|
|
2147
|
-
bucket.push(candidate);
|
|
2148
|
-
candidatesByFirstCharacter.set(first, bucket);
|
|
2149
|
-
}
|
|
2150
|
-
let redacted = "";
|
|
2151
|
-
let offset = 0;
|
|
2152
|
-
while (offset < text.length) {
|
|
2153
|
-
const match = candidatesByFirstCharacter.get(text[offset] ?? "")?.find((candidate) => text.startsWith(candidate.form, offset));
|
|
2154
|
-
if (!match) {
|
|
2155
|
-
redacted += text[offset];
|
|
2156
|
-
offset += 1;
|
|
2157
|
-
continue;
|
|
2158
|
-
}
|
|
2159
|
-
redacted += `[REDACTED:${match.key}]`;
|
|
2160
|
-
offset += match.form.length;
|
|
2161
|
-
}
|
|
2162
|
-
return redacted;
|
|
2163
|
-
}
|
|
2164
|
-
var vaultDb, vaultMasterKey, VAULT_KEY_PATTERN, VAULT_VALUE_MIN_BYTES = 4, VAULT_VALUE_MAX_BYTES, VAULT_DESCRIPTION_MAX_LENGTH = 500;
|
|
2165
|
-
var init_vault = __esm(async () => {
|
|
2166
|
-
init_config();
|
|
2167
|
-
await init_sqlite();
|
|
2168
|
-
init_vault_crypto();
|
|
2169
|
-
vaultMasterKey = VAULT_MASTER_KEY;
|
|
2170
|
-
VAULT_KEY_PATTERN = /^[A-Z][A-Z0-9_]{0,127}$/;
|
|
2171
|
-
VAULT_VALUE_MAX_BYTES = 64 * 1024;
|
|
2172
|
-
});
|
|
2173
|
-
|
|
2174
1961
|
// ../../packages/core/src/agents/vault-tool-policy.ts
|
|
2175
1962
|
function createVaultToolPolicy(host) {
|
|
2176
1963
|
function isVaultBrokerTool(toolName) {
|
|
@@ -2191,21 +1978,23 @@ function createVaultToolPolicy(host) {
|
|
|
2191
1978
|
return false;
|
|
2192
1979
|
}
|
|
2193
1980
|
function shouldRedirectVaultTool(userId, toolName, input) {
|
|
2194
|
-
return
|
|
1981
|
+
return false;
|
|
2195
1982
|
}
|
|
2196
1983
|
return { isVaultBrokerTool, referencesRuntimeSecretStorage, shouldRedirectVaultTool };
|
|
2197
1984
|
}
|
|
2198
1985
|
var SENSITIVE_RUNTIME_NAMES, defaultVaultToolPolicy, isVaultBrokerTool, referencesRuntimeSecretStorage, shouldRedirectVaultTool;
|
|
2199
|
-
var init_vault_tool_policy = __esm(
|
|
1986
|
+
var init_vault_tool_policy = __esm(() => {
|
|
2200
1987
|
init_sensitive_path();
|
|
2201
|
-
await init_vault();
|
|
2202
1988
|
SENSITIVE_RUNTIME_NAMES = [
|
|
2203
1989
|
"vault.db",
|
|
2204
1990
|
"vault-master-key",
|
|
2205
1991
|
"runtime-mcp-secret",
|
|
2206
1992
|
"sessions.db"
|
|
2207
1993
|
];
|
|
2208
|
-
defaultVaultToolPolicy = createVaultToolPolicy({
|
|
1994
|
+
defaultVaultToolPolicy = createVaultToolPolicy({
|
|
1995
|
+
isSensitivePath,
|
|
1996
|
+
valueReferencesVaultKey: () => false
|
|
1997
|
+
});
|
|
2209
1998
|
isVaultBrokerTool = defaultVaultToolPolicy.isVaultBrokerTool;
|
|
2210
1999
|
referencesRuntimeSecretStorage = defaultVaultToolPolicy.referencesRuntimeSecretStorage;
|
|
2211
2000
|
shouldRedirectVaultTool = defaultVaultToolPolicy.shouldRedirectVaultTool;
|
|
@@ -2366,7 +2155,7 @@ var init_context = () => {};
|
|
|
2366
2155
|
|
|
2367
2156
|
// ../../packages/core/src/platform/background-bash/manager.ts
|
|
2368
2157
|
import { execFileSync as execFileSync3, spawn } from "child_process";
|
|
2369
|
-
import { randomBytes as
|
|
2158
|
+
import { randomBytes as randomBytes3 } from "crypto";
|
|
2370
2159
|
function makeBgBashKey(_userId, _topic) {
|
|
2371
2160
|
return "runtime";
|
|
2372
2161
|
}
|
|
@@ -2383,8 +2172,8 @@ function createBackgroundBashManager(options = {}) {
|
|
|
2383
2172
|
const usedPorts = new Set;
|
|
2384
2173
|
const spawning = new Map;
|
|
2385
2174
|
const knownContexts = new Map;
|
|
2386
|
-
const runtimeCapability = options.capability ??
|
|
2387
|
-
const runtimeServerId = options.serverId ??
|
|
2175
|
+
const runtimeCapability = options.capability ?? randomBytes3(32).toString("hex");
|
|
2176
|
+
const runtimeServerId = options.serverId ?? randomBytes3(16).toString("hex");
|
|
2388
2177
|
const serverFile = options.serverFile ?? BACKGROUND_BASH_SERVER;
|
|
2389
2178
|
const basePort = options.basePort ?? BG_BASH_BASE_PORT;
|
|
2390
2179
|
const maxPort = options.maxPort ?? BG_BASH_MAX_PORT;
|
|
@@ -3074,7 +2863,9 @@ var init_mcp_config = __esm(() => {
|
|
|
3074
2863
|
vault: {
|
|
3075
2864
|
...commonRuntimeMcpPolicy("vault"),
|
|
3076
2865
|
build({ userId, agent }) {
|
|
3077
|
-
const args = [`--user-id=${userId}
|
|
2866
|
+
const args = [`--user-id=${userId}`];
|
|
2867
|
+
if (agent !== "codex")
|
|
2868
|
+
args.push("--list-only=true");
|
|
3078
2869
|
return buildStdioMcpServer(agent, VAULT_SERVER, args);
|
|
3079
2870
|
}
|
|
3080
2871
|
}
|
|
@@ -3088,6 +2879,200 @@ var init_mcp_config = __esm(() => {
|
|
|
3088
2879
|
nodeMcpEntries = [];
|
|
3089
2880
|
});
|
|
3090
2881
|
|
|
2882
|
+
// ../../packages/core/src/storage/vault-crypto.ts
|
|
2883
|
+
import { createCipheriv, createDecipheriv, createHash, randomBytes as randomBytes4 } from "crypto";
|
|
2884
|
+
function encryptionKey(masterKey = VAULT_MASTER_KEY) {
|
|
2885
|
+
return createHash("sha256").update("otium-vault-value-v1\x00", "utf8").update(masterKey, "utf8").digest().subarray(0, KEY_BYTES);
|
|
2886
|
+
}
|
|
2887
|
+
function aad(userId, key) {
|
|
2888
|
+
return Buffer.from(`${userId}\x00${key.toUpperCase()}`, "utf8");
|
|
2889
|
+
}
|
|
2890
|
+
function isEncryptedVaultValue(value) {
|
|
2891
|
+
return value.startsWith(ENVELOPE_PREFIX);
|
|
2892
|
+
}
|
|
2893
|
+
function encryptVaultValue(userId, key, value, masterKey = VAULT_MASTER_KEY) {
|
|
2894
|
+
const iv = randomBytes4(IV_BYTES);
|
|
2895
|
+
const cipher = createCipheriv("aes-256-gcm", encryptionKey(masterKey), iv);
|
|
2896
|
+
cipher.setAAD(aad(userId, key));
|
|
2897
|
+
const ciphertext = Buffer.concat([cipher.update(value, "utf8"), cipher.final()]);
|
|
2898
|
+
const tag = cipher.getAuthTag();
|
|
2899
|
+
return `${ENVELOPE_PREFIX}${iv.toString("base64url")}.${ciphertext.toString("base64url")}.${tag.toString("base64url")}`;
|
|
2900
|
+
}
|
|
2901
|
+
function decryptVaultValue(userId, key, storedValue, masterKey = VAULT_MASTER_KEY) {
|
|
2902
|
+
if (!isEncryptedVaultValue(storedValue)) {
|
|
2903
|
+
return { value: storedValue, legacyPlaintext: true };
|
|
2904
|
+
}
|
|
2905
|
+
const encoded = storedValue.slice(ENVELOPE_PREFIX.length);
|
|
2906
|
+
const [ivPart, ciphertextPart, tagPart, ...extra] = encoded.split(".");
|
|
2907
|
+
if (!ivPart || ciphertextPart === undefined || !tagPart || extra.length > 0) {
|
|
2908
|
+
throw new Error("Invalid encrypted vault value");
|
|
2909
|
+
}
|
|
2910
|
+
const iv = Buffer.from(ivPart, "base64url");
|
|
2911
|
+
const ciphertext = Buffer.from(ciphertextPart, "base64url");
|
|
2912
|
+
const tag = Buffer.from(tagPart, "base64url");
|
|
2913
|
+
if (iv.length !== IV_BYTES || tag.length !== 16) {
|
|
2914
|
+
throw new Error("Invalid encrypted vault value");
|
|
2915
|
+
}
|
|
2916
|
+
const decipher = createDecipheriv("aes-256-gcm", encryptionKey(masterKey), iv);
|
|
2917
|
+
decipher.setAAD(aad(userId, key));
|
|
2918
|
+
decipher.setAuthTag(tag);
|
|
2919
|
+
const plaintext = Buffer.concat([decipher.update(ciphertext), decipher.final()]);
|
|
2920
|
+
return { value: plaintext.toString("utf8"), legacyPlaintext: false };
|
|
2921
|
+
}
|
|
2922
|
+
var ENVELOPE_PREFIX = "otium-vault:v1:", IV_BYTES = 12, KEY_BYTES = 32;
|
|
2923
|
+
var init_vault_crypto = __esm(() => {
|
|
2924
|
+
init_config();
|
|
2925
|
+
});
|
|
2926
|
+
|
|
2927
|
+
// ../../packages/core/src/storage/vault.ts
|
|
2928
|
+
import { chmodSync as chmodSync2, mkdirSync as mkdirSync7 } from "fs";
|
|
2929
|
+
import { join as join10 } from "path";
|
|
2930
|
+
function initializeVaultDatabase(database) {
|
|
2931
|
+
database.exec("PRAGMA journal_mode = WAL");
|
|
2932
|
+
database.exec("PRAGMA busy_timeout = 5000");
|
|
2933
|
+
database.exec(`
|
|
2934
|
+
CREATE TABLE IF NOT EXISTS vault (
|
|
2935
|
+
user_id TEXT NOT NULL,
|
|
2936
|
+
key TEXT NOT NULL,
|
|
2937
|
+
value TEXT NOT NULL,
|
|
2938
|
+
description TEXT NOT NULL DEFAULT '',
|
|
2939
|
+
PRIMARY KEY (user_id, key)
|
|
2940
|
+
)
|
|
2941
|
+
`);
|
|
2942
|
+
{
|
|
2943
|
+
const cols = database.prepare("PRAGMA table_info(vault)").all();
|
|
2944
|
+
const uid = cols.find((c) => c.name === "user_id");
|
|
2945
|
+
if (uid && uid.type.toUpperCase() === "INTEGER") {
|
|
2946
|
+
database.exec("BEGIN");
|
|
2947
|
+
database.exec(`
|
|
2948
|
+
CREATE TABLE vault_migrated (
|
|
2949
|
+
user_id TEXT NOT NULL,
|
|
2950
|
+
key TEXT NOT NULL,
|
|
2951
|
+
value TEXT NOT NULL,
|
|
2952
|
+
description TEXT NOT NULL DEFAULT '',
|
|
2953
|
+
PRIMARY KEY (user_id, key)
|
|
2954
|
+
)
|
|
2955
|
+
`);
|
|
2956
|
+
database.exec("INSERT INTO vault_migrated SELECT CAST(user_id AS TEXT), key, value, description FROM vault");
|
|
2957
|
+
database.exec("DROP TABLE vault");
|
|
2958
|
+
database.exec("ALTER TABLE vault_migrated RENAME TO vault");
|
|
2959
|
+
database.exec("COMMIT");
|
|
2960
|
+
}
|
|
2961
|
+
}
|
|
2962
|
+
}
|
|
2963
|
+
function openVaultDatabase(dataDir) {
|
|
2964
|
+
const path = join10(dataDir, "vault.db");
|
|
2965
|
+
mkdirSync7(dataDir, { recursive: true });
|
|
2966
|
+
const database = new Database(path, { create: true });
|
|
2967
|
+
chmodSync2(path, 384);
|
|
2968
|
+
initializeVaultDatabase(database);
|
|
2969
|
+
return database;
|
|
2970
|
+
}
|
|
2971
|
+
function activeVaultDatabase() {
|
|
2972
|
+
if (!vaultDb)
|
|
2973
|
+
vaultDb = openVaultDatabase(DATA_DIR);
|
|
2974
|
+
return vaultDb;
|
|
2975
|
+
}
|
|
2976
|
+
function normalizeVaultKey(key) {
|
|
2977
|
+
return key.trim().toUpperCase();
|
|
2978
|
+
}
|
|
2979
|
+
function validateVaultKey(key) {
|
|
2980
|
+
return VAULT_KEY_PATTERN.test(normalizeVaultKey(key));
|
|
2981
|
+
}
|
|
2982
|
+
function decryptRow(userId, key, storedValue) {
|
|
2983
|
+
const database = activeVaultDatabase();
|
|
2984
|
+
const decoded = decryptVaultValue(userId, key, storedValue, vaultMasterKey);
|
|
2985
|
+
if (decoded.legacyPlaintext) {
|
|
2986
|
+
database.prepare("UPDATE vault SET value = ? WHERE user_id = ? AND key = ? AND value = ?").run(encryptVaultValue(userId, key, decoded.value, vaultMasterKey), userId, key, storedValue);
|
|
2987
|
+
}
|
|
2988
|
+
return decoded.value;
|
|
2989
|
+
}
|
|
2990
|
+
function vaultListWithValues(userId) {
|
|
2991
|
+
const rows = activeVaultDatabase().prepare("SELECT key, description, value FROM vault WHERE user_id = ? ORDER BY key").all(userId);
|
|
2992
|
+
return rows.map((row) => ({
|
|
2993
|
+
key: row.key,
|
|
2994
|
+
description: row.description,
|
|
2995
|
+
value: decryptRow(userId, row.key, row.value)
|
|
2996
|
+
}));
|
|
2997
|
+
}
|
|
2998
|
+
function vaultSet(userId, key, value, description = "") {
|
|
2999
|
+
const normalizedKey = normalizeVaultKey(key);
|
|
3000
|
+
activeVaultDatabase().prepare(`INSERT INTO vault (user_id, key, value, description) VALUES (?, ?, ?, ?)
|
|
3001
|
+
ON CONFLICT (user_id, key) DO UPDATE
|
|
3002
|
+
SET value = excluded.value, description = excluded.description`).run(userId, normalizedKey, encryptVaultValue(userId, normalizedKey, value, vaultMasterKey), description);
|
|
3003
|
+
}
|
|
3004
|
+
function vaultHasKey(userId, key) {
|
|
3005
|
+
const row = activeVaultDatabase().prepare("SELECT 1 FROM vault WHERE user_id = ? AND key = ?").get(userId, normalizeVaultKey(key));
|
|
3006
|
+
return row != null;
|
|
3007
|
+
}
|
|
3008
|
+
function vaultDel(userId, key) {
|
|
3009
|
+
const result = activeVaultDatabase().prepare("DELETE FROM vault WHERE user_id = ? AND key = ?").run(userId, normalizeVaultKey(key));
|
|
3010
|
+
return result.changes > 0;
|
|
3011
|
+
}
|
|
3012
|
+
function vaultList(userId) {
|
|
3013
|
+
return activeVaultDatabase().prepare("SELECT key, description FROM vault WHERE user_id = ? ORDER BY key").all(userId);
|
|
3014
|
+
}
|
|
3015
|
+
function vaultSubstituteDetailed(userId, text) {
|
|
3016
|
+
const entries = new Map(vaultListWithValues(userId).map((entry) => [entry.key, entry.value]));
|
|
3017
|
+
const usedKeys = new Set;
|
|
3018
|
+
const substituted = text.replace(/\{\{([^}]+)\}\}/g, (match, rawKey) => {
|
|
3019
|
+
const key = normalizeVaultKey(rawKey);
|
|
3020
|
+
const value = entries.get(key);
|
|
3021
|
+
if (value === undefined)
|
|
3022
|
+
return match;
|
|
3023
|
+
usedKeys.add(key);
|
|
3024
|
+
return value;
|
|
3025
|
+
});
|
|
3026
|
+
return { text: substituted, usedKeys: [...usedKeys] };
|
|
3027
|
+
}
|
|
3028
|
+
function encodedSecretForms(value) {
|
|
3029
|
+
const forms = new Set([
|
|
3030
|
+
value,
|
|
3031
|
+
encodeURIComponent(value),
|
|
3032
|
+
Buffer.from(value, "utf8").toString("base64"),
|
|
3033
|
+
Buffer.from(value, "utf8").toString("base64url"),
|
|
3034
|
+
Buffer.from(value, "utf8").toString("hex")
|
|
3035
|
+
]);
|
|
3036
|
+
forms.delete("");
|
|
3037
|
+
return [...forms].sort((a, b) => b.length - a.length);
|
|
3038
|
+
}
|
|
3039
|
+
function redactVaultSecrets(userId, text) {
|
|
3040
|
+
const candidates = vaultListWithValues(userId).flatMap((entry) => encodedSecretForms(entry.value).map((form) => ({ form, key: entry.key }))).sort((a, b) => b.form.length - a.form.length || a.key.localeCompare(b.key));
|
|
3041
|
+
if (candidates.length === 0)
|
|
3042
|
+
return text;
|
|
3043
|
+
const candidatesByFirstCharacter = new Map;
|
|
3044
|
+
for (const candidate of candidates) {
|
|
3045
|
+
const first = candidate.form[0];
|
|
3046
|
+
if (!first)
|
|
3047
|
+
continue;
|
|
3048
|
+
const bucket = candidatesByFirstCharacter.get(first) ?? [];
|
|
3049
|
+
bucket.push(candidate);
|
|
3050
|
+
candidatesByFirstCharacter.set(first, bucket);
|
|
3051
|
+
}
|
|
3052
|
+
let redacted = "";
|
|
3053
|
+
let offset = 0;
|
|
3054
|
+
while (offset < text.length) {
|
|
3055
|
+
const match = candidatesByFirstCharacter.get(text[offset] ?? "")?.find((candidate) => text.startsWith(candidate.form, offset));
|
|
3056
|
+
if (!match) {
|
|
3057
|
+
redacted += text[offset];
|
|
3058
|
+
offset += 1;
|
|
3059
|
+
continue;
|
|
3060
|
+
}
|
|
3061
|
+
redacted += `[REDACTED:${match.key}]`;
|
|
3062
|
+
offset += match.form.length;
|
|
3063
|
+
}
|
|
3064
|
+
return redacted;
|
|
3065
|
+
}
|
|
3066
|
+
var vaultDb, vaultMasterKey, VAULT_KEY_PATTERN, VAULT_VALUE_MIN_BYTES = 4, VAULT_VALUE_MAX_BYTES, VAULT_DESCRIPTION_MAX_LENGTH = 500;
|
|
3067
|
+
var init_vault = __esm(async () => {
|
|
3068
|
+
init_config();
|
|
3069
|
+
await init_sqlite();
|
|
3070
|
+
init_vault_crypto();
|
|
3071
|
+
vaultMasterKey = VAULT_MASTER_KEY;
|
|
3072
|
+
VAULT_KEY_PATTERN = /^[A-Z][A-Z0-9_]{0,127}$/;
|
|
3073
|
+
VAULT_VALUE_MAX_BYTES = 64 * 1024;
|
|
3074
|
+
});
|
|
3075
|
+
|
|
3091
3076
|
// ../../packages/core/src/agents/execution-host.ts
|
|
3092
3077
|
import { AsyncLocalStorage } from "async_hooks";
|
|
3093
3078
|
function activeHost() {
|
|
@@ -3119,7 +3104,7 @@ function hostedCodexAuthFilePath() {
|
|
|
3119
3104
|
}
|
|
3120
3105
|
var defaultHost, hostRegistrations, scopedHost;
|
|
3121
3106
|
var init_execution_host = __esm(async () => {
|
|
3122
|
-
|
|
3107
|
+
init_vault_tool_policy();
|
|
3123
3108
|
init_config();
|
|
3124
3109
|
init_mcp_config();
|
|
3125
3110
|
await init_vault();
|
|
@@ -3707,7 +3692,7 @@ var init_claude_provider = __esm(async () => {
|
|
|
3707
3692
|
});
|
|
3708
3693
|
|
|
3709
3694
|
// ../../packages/core/src/version.ts
|
|
3710
|
-
var NEGOTIUM_VERSION = "0.1.
|
|
3695
|
+
var NEGOTIUM_VERSION = "0.1.22";
|
|
3711
3696
|
|
|
3712
3697
|
// ../../packages/core/src/agents/codex-native-multi-agent.ts
|
|
3713
3698
|
import { spawn as spawn3 } from "child_process";
|
|
@@ -6697,47 +6682,186 @@ function getBrowserProfileOwner(topicId, fallbackUserId) {
|
|
|
6697
6682
|
function isTopicBrowserProfileOwner(topicId, userId) {
|
|
6698
6683
|
return getBrowserProfileOwner(topicId, "") === userId;
|
|
6699
6684
|
}
|
|
6700
|
-
function getTopicBrowserProfile(topicId) {
|
|
6701
|
-
const value = db.query("SELECT browser_profile FROM api_topics WHERE id = ?").get(topicId)?.browser_profile;
|
|
6702
|
-
return value || DEFAULT_BROWSER_PROFILE;
|
|
6685
|
+
function getTopicBrowserProfile(topicId) {
|
|
6686
|
+
const value = db.query("SELECT browser_profile FROM api_topics WHERE id = ?").get(topicId)?.browser_profile;
|
|
6687
|
+
return value || DEFAULT_BROWSER_PROFILE;
|
|
6688
|
+
}
|
|
6689
|
+
function hasBrowserProfileTopic(topicId) {
|
|
6690
|
+
return Boolean(db.query("SELECT id FROM api_topics WHERE id = ?").get(topicId));
|
|
6691
|
+
}
|
|
6692
|
+
function createBrowserProfile(ownerId, rawName) {
|
|
6693
|
+
const name = normalizeBrowserProfileName(rawName);
|
|
6694
|
+
if (name === DEFAULT_BROWSER_PROFILE)
|
|
6695
|
+
return name;
|
|
6696
|
+
db.query("INSERT OR IGNORE INTO browser_profiles (owner_id, name, created_at) VALUES (?, ?, ?)").run(ownerId, name, new Date().toISOString());
|
|
6697
|
+
return name;
|
|
6698
|
+
}
|
|
6699
|
+
function assignTopicBrowserProfile(opts) {
|
|
6700
|
+
const actualOwner = getBrowserProfileOwner(opts.topicId, "");
|
|
6701
|
+
if (!actualOwner)
|
|
6702
|
+
throw new Error(`Topic "${opts.topicId}" not found or has no owner.`);
|
|
6703
|
+
if (actualOwner !== opts.actorUserId) {
|
|
6704
|
+
throw new Error(`Only the topic owner can change its browser profile.`);
|
|
6705
|
+
}
|
|
6706
|
+
const profile = normalizeBrowserProfileName(opts.profile);
|
|
6707
|
+
if (profile !== DEFAULT_BROWSER_PROFILE)
|
|
6708
|
+
createBrowserProfile(actualOwner, profile);
|
|
6709
|
+
const previous = getTopicBrowserProfile(opts.topicId);
|
|
6710
|
+
const result = db.query("UPDATE api_topics SET browser_profile = ? WHERE id = ?").run(profile, opts.topicId);
|
|
6711
|
+
if (Number(result.changes ?? 0) !== 1)
|
|
6712
|
+
throw new Error(`Topic "${opts.topicId}" not found.`);
|
|
6713
|
+
return { previous, profile };
|
|
6714
|
+
}
|
|
6715
|
+
var DEFAULT_BROWSER_PROFILE = "default";
|
|
6716
|
+
var init_browser_profiles = __esm(async () => {
|
|
6717
|
+
await init_forum_db();
|
|
6718
|
+
db.exec(`
|
|
6719
|
+
CREATE TABLE IF NOT EXISTS browser_profiles (
|
|
6720
|
+
owner_id TEXT NOT NULL,
|
|
6721
|
+
name TEXT NOT NULL,
|
|
6722
|
+
created_at TEXT NOT NULL,
|
|
6723
|
+
PRIMARY KEY (owner_id, name)
|
|
6724
|
+
)
|
|
6725
|
+
`);
|
|
6726
|
+
});
|
|
6727
|
+
|
|
6728
|
+
// ../../packages/core/src/storage/runtime-process-leases.ts
|
|
6729
|
+
function rowToLease2(row) {
|
|
6730
|
+
return {
|
|
6731
|
+
role: row.role,
|
|
6732
|
+
ownerId: row.owner_id,
|
|
6733
|
+
pid: Number(row.pid),
|
|
6734
|
+
startedAt: Number(row.started_at),
|
|
6735
|
+
heartbeatAt: Number(row.heartbeat_at)
|
|
6736
|
+
};
|
|
6737
|
+
}
|
|
6738
|
+
function isProcessAlive(pid) {
|
|
6739
|
+
if (!Number.isSafeInteger(pid) || pid <= 0)
|
|
6740
|
+
return false;
|
|
6741
|
+
try {
|
|
6742
|
+
process.kill(pid, 0);
|
|
6743
|
+
return true;
|
|
6744
|
+
} catch (error) {
|
|
6745
|
+
return error.code !== "ESRCH";
|
|
6746
|
+
}
|
|
6747
|
+
}
|
|
6748
|
+
function removeDeadRuntimeProcessLease(role) {
|
|
6749
|
+
const row = db.query("SELECT * FROM runtime_process_leases WHERE role = ?").get(role);
|
|
6750
|
+
if (!row)
|
|
6751
|
+
return;
|
|
6752
|
+
const lease = rowToLease2(row);
|
|
6753
|
+
if (isProcessAlive(lease.pid))
|
|
6754
|
+
return;
|
|
6755
|
+
db.query(`DELETE FROM runtime_process_leases
|
|
6756
|
+
WHERE role = ? AND owner_id = ? AND pid = ? AND heartbeat_at = ?`).run(lease.role, lease.ownerId, lease.pid, lease.heartbeatAt);
|
|
6757
|
+
}
|
|
6758
|
+
function getRuntimeProcessLease(role, now = Date.now(), staleMs = PROCESS_LEASE_STALE_MS) {
|
|
6759
|
+
const row = db.query("SELECT * FROM runtime_process_leases WHERE role = ?").get(role);
|
|
6760
|
+
if (!row)
|
|
6761
|
+
return null;
|
|
6762
|
+
const lease = rowToLease2(row);
|
|
6763
|
+
return now - lease.heartbeatAt <= staleMs ? lease : null;
|
|
6764
|
+
}
|
|
6765
|
+
function listRuntimeProcessLeases(rolePrefix = "", now = Date.now(), staleMs = PROCESS_LEASE_STALE_MS) {
|
|
6766
|
+
const rows = rolePrefix ? db.query("SELECT * FROM runtime_process_leases WHERE role LIKE ? ORDER BY role").all(`${rolePrefix}%`) : db.query("SELECT * FROM runtime_process_leases ORDER BY role").all();
|
|
6767
|
+
return rows.map(rowToLease2).filter((lease) => now - lease.heartbeatAt <= staleMs);
|
|
6768
|
+
}
|
|
6769
|
+
function heartbeatRuntimeProcessLease(role, ownerId, now = Date.now()) {
|
|
6770
|
+
const result = db.query(`UPDATE runtime_process_leases
|
|
6771
|
+
SET heartbeat_at = ?
|
|
6772
|
+
WHERE role = ? AND owner_id = ?`).run(now, role, ownerId);
|
|
6773
|
+
return Number(result.changes ?? 0) > 0;
|
|
6703
6774
|
}
|
|
6704
|
-
function
|
|
6705
|
-
|
|
6775
|
+
function releaseRuntimeProcessLease(role, ownerId) {
|
|
6776
|
+
const result = db.query("DELETE FROM runtime_process_leases WHERE role = ? AND owner_id = ?").run(role, ownerId);
|
|
6777
|
+
return Number(result.changes ?? 0) > 0;
|
|
6706
6778
|
}
|
|
6707
|
-
function
|
|
6708
|
-
const
|
|
6709
|
-
if (
|
|
6710
|
-
|
|
6711
|
-
|
|
6712
|
-
|
|
6779
|
+
function acquireRuntimeProcessLease(role, options = {}) {
|
|
6780
|
+
const normalizedRole = role.trim();
|
|
6781
|
+
if (!normalizedRole)
|
|
6782
|
+
throw new Error("runtime process lease role must not be empty");
|
|
6783
|
+
removeDeadRuntimeProcessLease(normalizedRole);
|
|
6784
|
+
const ownerId = options.ownerId ?? `${RUNTIME_INSTANCE_ID}:${normalizedRole}`;
|
|
6785
|
+
const pid = options.pid ?? process.pid;
|
|
6786
|
+
const now = options.now ?? Date.now();
|
|
6787
|
+
const staleMs = options.staleMs ?? PROCESS_LEASE_STALE_MS;
|
|
6788
|
+
const result = db.query(`INSERT INTO runtime_process_leases (role, owner_id, pid, started_at, heartbeat_at)
|
|
6789
|
+
VALUES (?, ?, ?, ?, ?)
|
|
6790
|
+
ON CONFLICT(role) DO UPDATE SET
|
|
6791
|
+
owner_id = excluded.owner_id,
|
|
6792
|
+
pid = excluded.pid,
|
|
6793
|
+
started_at = excluded.started_at,
|
|
6794
|
+
heartbeat_at = excluded.heartbeat_at
|
|
6795
|
+
WHERE runtime_process_leases.heartbeat_at < ?`).run(normalizedRole, ownerId, pid, now, now, now - staleMs);
|
|
6796
|
+
if (Number(result.changes ?? 0) === 0)
|
|
6797
|
+
return null;
|
|
6798
|
+
let stopped = false;
|
|
6799
|
+
const heartbeatMs = options.heartbeatMs ?? PROCESS_LEASE_HEARTBEAT_MS;
|
|
6800
|
+
const timer = setInterval(() => {
|
|
6801
|
+
if (stopped)
|
|
6802
|
+
return;
|
|
6803
|
+
if (heartbeatRuntimeProcessLease(normalizedRole, ownerId))
|
|
6804
|
+
return;
|
|
6805
|
+
stopped = true;
|
|
6806
|
+
clearInterval(timer);
|
|
6807
|
+
options.onLost?.();
|
|
6808
|
+
}, heartbeatMs);
|
|
6809
|
+
timer.unref?.();
|
|
6810
|
+
return {
|
|
6811
|
+
role: normalizedRole,
|
|
6812
|
+
ownerId,
|
|
6813
|
+
pid,
|
|
6814
|
+
startedAt: now,
|
|
6815
|
+
heartbeatAt: now,
|
|
6816
|
+
stop() {
|
|
6817
|
+
if (stopped)
|
|
6818
|
+
return;
|
|
6819
|
+
stopped = true;
|
|
6820
|
+
clearInterval(timer);
|
|
6821
|
+
releaseRuntimeProcessLease(normalizedRole, ownerId);
|
|
6822
|
+
}
|
|
6823
|
+
};
|
|
6713
6824
|
}
|
|
6714
|
-
function
|
|
6715
|
-
const
|
|
6716
|
-
|
|
6717
|
-
|
|
6718
|
-
|
|
6719
|
-
|
|
6825
|
+
async function waitForRuntimeProcessLease(role, options = {}) {
|
|
6826
|
+
const {
|
|
6827
|
+
waitMs = PROCESS_LEASE_STALE_MS + PROCESS_LEASE_HEARTBEAT_MS,
|
|
6828
|
+
retryMs = 100,
|
|
6829
|
+
...acquireOptions
|
|
6830
|
+
} = options;
|
|
6831
|
+
const deadline = Date.now() + Math.max(0, waitMs);
|
|
6832
|
+
const interval = Math.max(1, retryMs);
|
|
6833
|
+
while (true) {
|
|
6834
|
+
const lease = acquireRuntimeProcessLease(role, acquireOptions);
|
|
6835
|
+
if (lease)
|
|
6836
|
+
return lease;
|
|
6837
|
+
const remaining = deadline - Date.now();
|
|
6838
|
+
if (remaining <= 0)
|
|
6839
|
+
return null;
|
|
6840
|
+
await new Promise((resolve5) => setTimeout(resolve5, Math.min(interval, remaining)));
|
|
6720
6841
|
}
|
|
6721
|
-
const profile = normalizeBrowserProfileName(opts.profile);
|
|
6722
|
-
if (profile !== DEFAULT_BROWSER_PROFILE)
|
|
6723
|
-
createBrowserProfile(actualOwner, profile);
|
|
6724
|
-
const previous = getTopicBrowserProfile(opts.topicId);
|
|
6725
|
-
const result = db.query("UPDATE api_topics SET browser_profile = ? WHERE id = ?").run(profile, opts.topicId);
|
|
6726
|
-
if (Number(result.changes ?? 0) !== 1)
|
|
6727
|
-
throw new Error(`Topic "${opts.topicId}" not found.`);
|
|
6728
|
-
return { previous, profile };
|
|
6729
6842
|
}
|
|
6730
|
-
|
|
6731
|
-
|
|
6843
|
+
async function waitForRequiredRuntimeProcessLease(role, options = {}) {
|
|
6844
|
+
const { workloadName = role, ...leaseOptions } = options;
|
|
6845
|
+
const lease = await waitForRuntimeProcessLease(role, leaseOptions);
|
|
6846
|
+
if (lease)
|
|
6847
|
+
return lease;
|
|
6848
|
+
const current2 = getRuntimeProcessLease(role);
|
|
6849
|
+
throw new Error(`${workloadName} is already running${current2 ? ` (pid ${current2.pid})` : ""}`);
|
|
6850
|
+
}
|
|
6851
|
+
var PROCESS_LEASE_STALE_MS = 5000, PROCESS_LEASE_HEARTBEAT_MS = 1000;
|
|
6852
|
+
var init_runtime_process_leases = __esm(async () => {
|
|
6732
6853
|
await init_forum_db();
|
|
6854
|
+
await init_runtime_leases();
|
|
6733
6855
|
db.exec(`
|
|
6734
|
-
CREATE TABLE IF NOT EXISTS
|
|
6735
|
-
|
|
6736
|
-
|
|
6737
|
-
|
|
6738
|
-
|
|
6856
|
+
CREATE TABLE IF NOT EXISTS runtime_process_leases (
|
|
6857
|
+
role TEXT PRIMARY KEY,
|
|
6858
|
+
owner_id TEXT NOT NULL UNIQUE,
|
|
6859
|
+
pid INTEGER NOT NULL,
|
|
6860
|
+
started_at INTEGER NOT NULL,
|
|
6861
|
+
heartbeat_at INTEGER NOT NULL
|
|
6739
6862
|
)
|
|
6740
6863
|
`);
|
|
6864
|
+
db.exec("CREATE INDEX IF NOT EXISTS idx_runtime_process_leases_heartbeat ON runtime_process_leases(heartbeat_at)");
|
|
6741
6865
|
});
|
|
6742
6866
|
|
|
6743
6867
|
// ../../packages/core/src/platform/playwright/manager.ts
|
|
@@ -7026,7 +7150,13 @@ function selectOrphanBrowserPids(procs, liveUserDataDirs, profileRoot, selfPid)
|
|
|
7026
7150
|
}
|
|
7027
7151
|
return out;
|
|
7028
7152
|
}
|
|
7153
|
+
function isBrowserJanitorOwner(leaseOwnerPid, selfPid) {
|
|
7154
|
+
return leaseOwnerPid === selfPid;
|
|
7155
|
+
}
|
|
7029
7156
|
function reapOrphanBrowsers() {
|
|
7157
|
+
const daemonLease = getRuntimeProcessLease("node-daemon", Date.now(), Number.POSITIVE_INFINITY);
|
|
7158
|
+
if (!isBrowserJanitorOwner(daemonLease?.pid ?? null, process.pid))
|
|
7159
|
+
return;
|
|
7030
7160
|
const profileRoot = resolve5(BROWSER_PROFILES_DIR);
|
|
7031
7161
|
let pids;
|
|
7032
7162
|
try {
|
|
@@ -7437,6 +7567,7 @@ var init_manager2 = __esm(async () => {
|
|
|
7437
7567
|
init_config();
|
|
7438
7568
|
init_logger();
|
|
7439
7569
|
await init_browser_profiles();
|
|
7570
|
+
await init_runtime_process_leases();
|
|
7440
7571
|
BASE_PORT = PLAYWRIGHT_BASE_PORT;
|
|
7441
7572
|
MAX_PORT = PLAYWRIGHT_MAX_PORT;
|
|
7442
7573
|
MAX_IDLE_MS = 2 * 60 * 60 * 1000;
|
|
@@ -15454,145 +15585,6 @@ var init_lifecycle2 = __esm(() => {
|
|
|
15454
15585
|
runShutdown = defaultLifecycle.runShutdown;
|
|
15455
15586
|
});
|
|
15456
15587
|
|
|
15457
|
-
// ../../packages/core/src/storage/runtime-process-leases.ts
|
|
15458
|
-
function rowToLease2(row) {
|
|
15459
|
-
return {
|
|
15460
|
-
role: row.role,
|
|
15461
|
-
ownerId: row.owner_id,
|
|
15462
|
-
pid: Number(row.pid),
|
|
15463
|
-
startedAt: Number(row.started_at),
|
|
15464
|
-
heartbeatAt: Number(row.heartbeat_at)
|
|
15465
|
-
};
|
|
15466
|
-
}
|
|
15467
|
-
function isProcessAlive(pid) {
|
|
15468
|
-
if (!Number.isSafeInteger(pid) || pid <= 0)
|
|
15469
|
-
return false;
|
|
15470
|
-
try {
|
|
15471
|
-
process.kill(pid, 0);
|
|
15472
|
-
return true;
|
|
15473
|
-
} catch (error) {
|
|
15474
|
-
return error.code !== "ESRCH";
|
|
15475
|
-
}
|
|
15476
|
-
}
|
|
15477
|
-
function removeDeadRuntimeProcessLease(role) {
|
|
15478
|
-
const row = db.query("SELECT * FROM runtime_process_leases WHERE role = ?").get(role);
|
|
15479
|
-
if (!row)
|
|
15480
|
-
return;
|
|
15481
|
-
const lease = rowToLease2(row);
|
|
15482
|
-
if (isProcessAlive(lease.pid))
|
|
15483
|
-
return;
|
|
15484
|
-
db.query(`DELETE FROM runtime_process_leases
|
|
15485
|
-
WHERE role = ? AND owner_id = ? AND pid = ? AND heartbeat_at = ?`).run(lease.role, lease.ownerId, lease.pid, lease.heartbeatAt);
|
|
15486
|
-
}
|
|
15487
|
-
function getRuntimeProcessLease(role, now = Date.now(), staleMs = PROCESS_LEASE_STALE_MS) {
|
|
15488
|
-
const row = db.query("SELECT * FROM runtime_process_leases WHERE role = ?").get(role);
|
|
15489
|
-
if (!row)
|
|
15490
|
-
return null;
|
|
15491
|
-
const lease = rowToLease2(row);
|
|
15492
|
-
return now - lease.heartbeatAt <= staleMs ? lease : null;
|
|
15493
|
-
}
|
|
15494
|
-
function listRuntimeProcessLeases(rolePrefix = "", now = Date.now(), staleMs = PROCESS_LEASE_STALE_MS) {
|
|
15495
|
-
const rows = rolePrefix ? db.query("SELECT * FROM runtime_process_leases WHERE role LIKE ? ORDER BY role").all(`${rolePrefix}%`) : db.query("SELECT * FROM runtime_process_leases ORDER BY role").all();
|
|
15496
|
-
return rows.map(rowToLease2).filter((lease) => now - lease.heartbeatAt <= staleMs);
|
|
15497
|
-
}
|
|
15498
|
-
function heartbeatRuntimeProcessLease(role, ownerId, now = Date.now()) {
|
|
15499
|
-
const result = db.query(`UPDATE runtime_process_leases
|
|
15500
|
-
SET heartbeat_at = ?
|
|
15501
|
-
WHERE role = ? AND owner_id = ?`).run(now, role, ownerId);
|
|
15502
|
-
return Number(result.changes ?? 0) > 0;
|
|
15503
|
-
}
|
|
15504
|
-
function releaseRuntimeProcessLease(role, ownerId) {
|
|
15505
|
-
const result = db.query("DELETE FROM runtime_process_leases WHERE role = ? AND owner_id = ?").run(role, ownerId);
|
|
15506
|
-
return Number(result.changes ?? 0) > 0;
|
|
15507
|
-
}
|
|
15508
|
-
function acquireRuntimeProcessLease(role, options = {}) {
|
|
15509
|
-
const normalizedRole = role.trim();
|
|
15510
|
-
if (!normalizedRole)
|
|
15511
|
-
throw new Error("runtime process lease role must not be empty");
|
|
15512
|
-
removeDeadRuntimeProcessLease(normalizedRole);
|
|
15513
|
-
const ownerId = options.ownerId ?? `${RUNTIME_INSTANCE_ID}:${normalizedRole}`;
|
|
15514
|
-
const pid = options.pid ?? process.pid;
|
|
15515
|
-
const now = options.now ?? Date.now();
|
|
15516
|
-
const staleMs = options.staleMs ?? PROCESS_LEASE_STALE_MS;
|
|
15517
|
-
const result = db.query(`INSERT INTO runtime_process_leases (role, owner_id, pid, started_at, heartbeat_at)
|
|
15518
|
-
VALUES (?, ?, ?, ?, ?)
|
|
15519
|
-
ON CONFLICT(role) DO UPDATE SET
|
|
15520
|
-
owner_id = excluded.owner_id,
|
|
15521
|
-
pid = excluded.pid,
|
|
15522
|
-
started_at = excluded.started_at,
|
|
15523
|
-
heartbeat_at = excluded.heartbeat_at
|
|
15524
|
-
WHERE runtime_process_leases.heartbeat_at < ?`).run(normalizedRole, ownerId, pid, now, now, now - staleMs);
|
|
15525
|
-
if (Number(result.changes ?? 0) === 0)
|
|
15526
|
-
return null;
|
|
15527
|
-
let stopped = false;
|
|
15528
|
-
const heartbeatMs = options.heartbeatMs ?? PROCESS_LEASE_HEARTBEAT_MS;
|
|
15529
|
-
const timer = setInterval(() => {
|
|
15530
|
-
if (stopped)
|
|
15531
|
-
return;
|
|
15532
|
-
if (heartbeatRuntimeProcessLease(normalizedRole, ownerId))
|
|
15533
|
-
return;
|
|
15534
|
-
stopped = true;
|
|
15535
|
-
clearInterval(timer);
|
|
15536
|
-
options.onLost?.();
|
|
15537
|
-
}, heartbeatMs);
|
|
15538
|
-
timer.unref?.();
|
|
15539
|
-
return {
|
|
15540
|
-
role: normalizedRole,
|
|
15541
|
-
ownerId,
|
|
15542
|
-
pid,
|
|
15543
|
-
startedAt: now,
|
|
15544
|
-
heartbeatAt: now,
|
|
15545
|
-
stop() {
|
|
15546
|
-
if (stopped)
|
|
15547
|
-
return;
|
|
15548
|
-
stopped = true;
|
|
15549
|
-
clearInterval(timer);
|
|
15550
|
-
releaseRuntimeProcessLease(normalizedRole, ownerId);
|
|
15551
|
-
}
|
|
15552
|
-
};
|
|
15553
|
-
}
|
|
15554
|
-
async function waitForRuntimeProcessLease(role, options = {}) {
|
|
15555
|
-
const {
|
|
15556
|
-
waitMs = PROCESS_LEASE_STALE_MS + PROCESS_LEASE_HEARTBEAT_MS,
|
|
15557
|
-
retryMs = 100,
|
|
15558
|
-
...acquireOptions
|
|
15559
|
-
} = options;
|
|
15560
|
-
const deadline = Date.now() + Math.max(0, waitMs);
|
|
15561
|
-
const interval = Math.max(1, retryMs);
|
|
15562
|
-
while (true) {
|
|
15563
|
-
const lease = acquireRuntimeProcessLease(role, acquireOptions);
|
|
15564
|
-
if (lease)
|
|
15565
|
-
return lease;
|
|
15566
|
-
const remaining = deadline - Date.now();
|
|
15567
|
-
if (remaining <= 0)
|
|
15568
|
-
return null;
|
|
15569
|
-
await new Promise((resolve9) => setTimeout(resolve9, Math.min(interval, remaining)));
|
|
15570
|
-
}
|
|
15571
|
-
}
|
|
15572
|
-
async function waitForRequiredRuntimeProcessLease(role, options = {}) {
|
|
15573
|
-
const { workloadName = role, ...leaseOptions } = options;
|
|
15574
|
-
const lease = await waitForRuntimeProcessLease(role, leaseOptions);
|
|
15575
|
-
if (lease)
|
|
15576
|
-
return lease;
|
|
15577
|
-
const current3 = getRuntimeProcessLease(role);
|
|
15578
|
-
throw new Error(`${workloadName} is already running${current3 ? ` (pid ${current3.pid})` : ""}`);
|
|
15579
|
-
}
|
|
15580
|
-
var PROCESS_LEASE_STALE_MS = 5000, PROCESS_LEASE_HEARTBEAT_MS = 1000;
|
|
15581
|
-
var init_runtime_process_leases = __esm(async () => {
|
|
15582
|
-
await init_forum_db();
|
|
15583
|
-
await init_runtime_leases();
|
|
15584
|
-
db.exec(`
|
|
15585
|
-
CREATE TABLE IF NOT EXISTS runtime_process_leases (
|
|
15586
|
-
role TEXT PRIMARY KEY,
|
|
15587
|
-
owner_id TEXT NOT NULL UNIQUE,
|
|
15588
|
-
pid INTEGER NOT NULL,
|
|
15589
|
-
started_at INTEGER NOT NULL,
|
|
15590
|
-
heartbeat_at INTEGER NOT NULL
|
|
15591
|
-
)
|
|
15592
|
-
`);
|
|
15593
|
-
db.exec("CREATE INDEX IF NOT EXISTS idx_runtime_process_leases_heartbeat ON runtime_process_leases(heartbeat_at)");
|
|
15594
|
-
});
|
|
15595
|
-
|
|
15596
15588
|
// ../../packages/core/src/platform/modules.ts
|
|
15597
15589
|
function startNegotiumNodeModules(modules, context) {
|
|
15598
15590
|
const seen = new Set;
|
|
@@ -20926,6 +20918,8 @@ async function runNodeDaemon(opts = {}) {
|
|
|
20926
20918
|
singleton: true
|
|
20927
20919
|
});
|
|
20928
20920
|
await node.completed;
|
|
20921
|
+
await new Promise((resolve13) => setImmediate(resolve13));
|
|
20922
|
+
process.exit(0);
|
|
20929
20923
|
}
|
|
20930
20924
|
var init_src5 = __esm(async () => {
|
|
20931
20925
|
await init_src();
|
|
@@ -32761,6 +32755,8 @@ async function runCanonicalNode(port) {
|
|
|
32761
32755
|
});
|
|
32762
32756
|
console.log(`negotium node listening on 127.0.0.1:${node.port} (ctrl-c to stop)`);
|
|
32763
32757
|
await node.completed;
|
|
32758
|
+
await new Promise((resolve15) => setImmediate(resolve15));
|
|
32759
|
+
process.exit(0);
|
|
32764
32760
|
}
|
|
32765
32761
|
async function stopAdapter(name) {
|
|
32766
32762
|
const { getRuntimeProcessLease: getRuntimeProcessLease2 } = await init_src().then(() => exports_src);
|
|
@@ -32889,4 +32885,4 @@ switch (command) {
|
|
|
32889
32885
|
}
|
|
32890
32886
|
}
|
|
32891
32887
|
|
|
32892
|
-
//# debugId=
|
|
32888
|
+
//# debugId=3240CD935FBF9D0764756E2164756E21
|