negotium 0.1.21 → 0.1.23
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 +199 -203
- package/dist/agent-helpers.js.map +10 -10
- package/dist/cron.js +249 -223
- package/dist/cron.js.map +12 -12
- package/dist/hosted-agent.js +249 -253
- package/dist/hosted-agent.js.map +11 -11
- package/dist/main.js +409 -398
- package/dist/main.js.map +14 -14
- 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/claude-provider.ts +11 -5
- package/dist/runtime/src/agents/maestro-provider.ts +4 -1
- package/dist/runtime/src/agents/vault-tool-policy.ts +28 -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/claude-provider.d.ts +1 -1
- package/dist/types/packages/core/src/agents/vault-tool-policy.d.ts +2 -1
- package/dist/types/packages/core/src/version.d.ts +1 -1
- package/package.json +1 -1
package/dist/main.js
CHANGED
|
@@ -1958,220 +1958,15 @@ var init_sensitive_path = __esm(() => {
|
|
|
1958
1958
|
];
|
|
1959
1959
|
});
|
|
1960
1960
|
|
|
1961
|
-
// ../../packages/core/src/
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
return
|
|
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);
|
|
1961
|
+
// ../../packages/core/src/agents/vault-tool-policy.ts
|
|
1962
|
+
function leafToolName(toolName) {
|
|
1963
|
+
const parts = toolName.split("__");
|
|
1964
|
+
return parts.at(-1) ?? toolName;
|
|
2136
1965
|
}
|
|
2137
|
-
function
|
|
2138
|
-
const
|
|
2139
|
-
|
|
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;
|
|
1966
|
+
function shouldSubstituteVaultToolInput(toolName) {
|
|
1967
|
+
const leaf = leafToolName(toolName);
|
|
1968
|
+
return leaf.startsWith("browser_") || DIRECT_VAULT_EXECUTION_TOOLS.has(leaf);
|
|
2163
1969
|
}
|
|
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
|
-
// ../../packages/core/src/agents/vault-tool-policy.ts
|
|
2175
1970
|
function createVaultToolPolicy(host) {
|
|
2176
1971
|
function isVaultBrokerTool(toolName) {
|
|
2177
1972
|
return toolName.includes("vault_run") || toolName.includes("vault_http_request");
|
|
@@ -2191,21 +1986,24 @@ function createVaultToolPolicy(host) {
|
|
|
2191
1986
|
return false;
|
|
2192
1987
|
}
|
|
2193
1988
|
function shouldRedirectVaultTool(userId, toolName, input) {
|
|
2194
|
-
return
|
|
1989
|
+
return false;
|
|
2195
1990
|
}
|
|
2196
1991
|
return { isVaultBrokerTool, referencesRuntimeSecretStorage, shouldRedirectVaultTool };
|
|
2197
1992
|
}
|
|
2198
|
-
var SENSITIVE_RUNTIME_NAMES, defaultVaultToolPolicy, isVaultBrokerTool, referencesRuntimeSecretStorage, shouldRedirectVaultTool;
|
|
2199
|
-
var init_vault_tool_policy = __esm(
|
|
1993
|
+
var SENSITIVE_RUNTIME_NAMES, DIRECT_VAULT_EXECUTION_TOOLS, defaultVaultToolPolicy, isVaultBrokerTool, referencesRuntimeSecretStorage, shouldRedirectVaultTool;
|
|
1994
|
+
var init_vault_tool_policy = __esm(() => {
|
|
2200
1995
|
init_sensitive_path();
|
|
2201
|
-
await init_vault();
|
|
2202
1996
|
SENSITIVE_RUNTIME_NAMES = [
|
|
2203
1997
|
"vault.db",
|
|
2204
1998
|
"vault-master-key",
|
|
2205
1999
|
"runtime-mcp-secret",
|
|
2206
2000
|
"sessions.db"
|
|
2207
2001
|
];
|
|
2208
|
-
|
|
2002
|
+
DIRECT_VAULT_EXECUTION_TOOLS = new Set(["Bash", "WebFetch"]);
|
|
2003
|
+
defaultVaultToolPolicy = createVaultToolPolicy({
|
|
2004
|
+
isSensitivePath,
|
|
2005
|
+
valueReferencesVaultKey: () => false
|
|
2006
|
+
});
|
|
2209
2007
|
isVaultBrokerTool = defaultVaultToolPolicy.isVaultBrokerTool;
|
|
2210
2008
|
referencesRuntimeSecretStorage = defaultVaultToolPolicy.referencesRuntimeSecretStorage;
|
|
2211
2009
|
shouldRedirectVaultTool = defaultVaultToolPolicy.shouldRedirectVaultTool;
|
|
@@ -2366,7 +2164,7 @@ var init_context = () => {};
|
|
|
2366
2164
|
|
|
2367
2165
|
// ../../packages/core/src/platform/background-bash/manager.ts
|
|
2368
2166
|
import { execFileSync as execFileSync3, spawn } from "child_process";
|
|
2369
|
-
import { randomBytes as
|
|
2167
|
+
import { randomBytes as randomBytes3 } from "crypto";
|
|
2370
2168
|
function makeBgBashKey(_userId, _topic) {
|
|
2371
2169
|
return "runtime";
|
|
2372
2170
|
}
|
|
@@ -2383,8 +2181,8 @@ function createBackgroundBashManager(options = {}) {
|
|
|
2383
2181
|
const usedPorts = new Set;
|
|
2384
2182
|
const spawning = new Map;
|
|
2385
2183
|
const knownContexts = new Map;
|
|
2386
|
-
const runtimeCapability = options.capability ??
|
|
2387
|
-
const runtimeServerId = options.serverId ??
|
|
2184
|
+
const runtimeCapability = options.capability ?? randomBytes3(32).toString("hex");
|
|
2185
|
+
const runtimeServerId = options.serverId ?? randomBytes3(16).toString("hex");
|
|
2388
2186
|
const serverFile = options.serverFile ?? BACKGROUND_BASH_SERVER;
|
|
2389
2187
|
const basePort = options.basePort ?? BG_BASH_BASE_PORT;
|
|
2390
2188
|
const maxPort = options.maxPort ?? BG_BASH_MAX_PORT;
|
|
@@ -3074,7 +2872,9 @@ var init_mcp_config = __esm(() => {
|
|
|
3074
2872
|
vault: {
|
|
3075
2873
|
...commonRuntimeMcpPolicy("vault"),
|
|
3076
2874
|
build({ userId, agent }) {
|
|
3077
|
-
const args = [`--user-id=${userId}
|
|
2875
|
+
const args = [`--user-id=${userId}`];
|
|
2876
|
+
if (agent !== "codex")
|
|
2877
|
+
args.push("--list-only=true");
|
|
3078
2878
|
return buildStdioMcpServer(agent, VAULT_SERVER, args);
|
|
3079
2879
|
}
|
|
3080
2880
|
}
|
|
@@ -3088,6 +2888,200 @@ var init_mcp_config = __esm(() => {
|
|
|
3088
2888
|
nodeMcpEntries = [];
|
|
3089
2889
|
});
|
|
3090
2890
|
|
|
2891
|
+
// ../../packages/core/src/storage/vault-crypto.ts
|
|
2892
|
+
import { createCipheriv, createDecipheriv, createHash, randomBytes as randomBytes4 } from "crypto";
|
|
2893
|
+
function encryptionKey(masterKey = VAULT_MASTER_KEY) {
|
|
2894
|
+
return createHash("sha256").update("otium-vault-value-v1\x00", "utf8").update(masterKey, "utf8").digest().subarray(0, KEY_BYTES);
|
|
2895
|
+
}
|
|
2896
|
+
function aad(userId, key) {
|
|
2897
|
+
return Buffer.from(`${userId}\x00${key.toUpperCase()}`, "utf8");
|
|
2898
|
+
}
|
|
2899
|
+
function isEncryptedVaultValue(value) {
|
|
2900
|
+
return value.startsWith(ENVELOPE_PREFIX);
|
|
2901
|
+
}
|
|
2902
|
+
function encryptVaultValue(userId, key, value, masterKey = VAULT_MASTER_KEY) {
|
|
2903
|
+
const iv = randomBytes4(IV_BYTES);
|
|
2904
|
+
const cipher = createCipheriv("aes-256-gcm", encryptionKey(masterKey), iv);
|
|
2905
|
+
cipher.setAAD(aad(userId, key));
|
|
2906
|
+
const ciphertext = Buffer.concat([cipher.update(value, "utf8"), cipher.final()]);
|
|
2907
|
+
const tag = cipher.getAuthTag();
|
|
2908
|
+
return `${ENVELOPE_PREFIX}${iv.toString("base64url")}.${ciphertext.toString("base64url")}.${tag.toString("base64url")}`;
|
|
2909
|
+
}
|
|
2910
|
+
function decryptVaultValue(userId, key, storedValue, masterKey = VAULT_MASTER_KEY) {
|
|
2911
|
+
if (!isEncryptedVaultValue(storedValue)) {
|
|
2912
|
+
return { value: storedValue, legacyPlaintext: true };
|
|
2913
|
+
}
|
|
2914
|
+
const encoded = storedValue.slice(ENVELOPE_PREFIX.length);
|
|
2915
|
+
const [ivPart, ciphertextPart, tagPart, ...extra] = encoded.split(".");
|
|
2916
|
+
if (!ivPart || ciphertextPart === undefined || !tagPart || extra.length > 0) {
|
|
2917
|
+
throw new Error("Invalid encrypted vault value");
|
|
2918
|
+
}
|
|
2919
|
+
const iv = Buffer.from(ivPart, "base64url");
|
|
2920
|
+
const ciphertext = Buffer.from(ciphertextPart, "base64url");
|
|
2921
|
+
const tag = Buffer.from(tagPart, "base64url");
|
|
2922
|
+
if (iv.length !== IV_BYTES || tag.length !== 16) {
|
|
2923
|
+
throw new Error("Invalid encrypted vault value");
|
|
2924
|
+
}
|
|
2925
|
+
const decipher = createDecipheriv("aes-256-gcm", encryptionKey(masterKey), iv);
|
|
2926
|
+
decipher.setAAD(aad(userId, key));
|
|
2927
|
+
decipher.setAuthTag(tag);
|
|
2928
|
+
const plaintext = Buffer.concat([decipher.update(ciphertext), decipher.final()]);
|
|
2929
|
+
return { value: plaintext.toString("utf8"), legacyPlaintext: false };
|
|
2930
|
+
}
|
|
2931
|
+
var ENVELOPE_PREFIX = "otium-vault:v1:", IV_BYTES = 12, KEY_BYTES = 32;
|
|
2932
|
+
var init_vault_crypto = __esm(() => {
|
|
2933
|
+
init_config();
|
|
2934
|
+
});
|
|
2935
|
+
|
|
2936
|
+
// ../../packages/core/src/storage/vault.ts
|
|
2937
|
+
import { chmodSync as chmodSync2, mkdirSync as mkdirSync7 } from "fs";
|
|
2938
|
+
import { join as join10 } from "path";
|
|
2939
|
+
function initializeVaultDatabase(database) {
|
|
2940
|
+
database.exec("PRAGMA journal_mode = WAL");
|
|
2941
|
+
database.exec("PRAGMA busy_timeout = 5000");
|
|
2942
|
+
database.exec(`
|
|
2943
|
+
CREATE TABLE IF NOT EXISTS vault (
|
|
2944
|
+
user_id TEXT NOT NULL,
|
|
2945
|
+
key TEXT NOT NULL,
|
|
2946
|
+
value TEXT NOT NULL,
|
|
2947
|
+
description TEXT NOT NULL DEFAULT '',
|
|
2948
|
+
PRIMARY KEY (user_id, key)
|
|
2949
|
+
)
|
|
2950
|
+
`);
|
|
2951
|
+
{
|
|
2952
|
+
const cols = database.prepare("PRAGMA table_info(vault)").all();
|
|
2953
|
+
const uid = cols.find((c) => c.name === "user_id");
|
|
2954
|
+
if (uid && uid.type.toUpperCase() === "INTEGER") {
|
|
2955
|
+
database.exec("BEGIN");
|
|
2956
|
+
database.exec(`
|
|
2957
|
+
CREATE TABLE vault_migrated (
|
|
2958
|
+
user_id TEXT NOT NULL,
|
|
2959
|
+
key TEXT NOT NULL,
|
|
2960
|
+
value TEXT NOT NULL,
|
|
2961
|
+
description TEXT NOT NULL DEFAULT '',
|
|
2962
|
+
PRIMARY KEY (user_id, key)
|
|
2963
|
+
)
|
|
2964
|
+
`);
|
|
2965
|
+
database.exec("INSERT INTO vault_migrated SELECT CAST(user_id AS TEXT), key, value, description FROM vault");
|
|
2966
|
+
database.exec("DROP TABLE vault");
|
|
2967
|
+
database.exec("ALTER TABLE vault_migrated RENAME TO vault");
|
|
2968
|
+
database.exec("COMMIT");
|
|
2969
|
+
}
|
|
2970
|
+
}
|
|
2971
|
+
}
|
|
2972
|
+
function openVaultDatabase(dataDir) {
|
|
2973
|
+
const path = join10(dataDir, "vault.db");
|
|
2974
|
+
mkdirSync7(dataDir, { recursive: true });
|
|
2975
|
+
const database = new Database(path, { create: true });
|
|
2976
|
+
chmodSync2(path, 384);
|
|
2977
|
+
initializeVaultDatabase(database);
|
|
2978
|
+
return database;
|
|
2979
|
+
}
|
|
2980
|
+
function activeVaultDatabase() {
|
|
2981
|
+
if (!vaultDb)
|
|
2982
|
+
vaultDb = openVaultDatabase(DATA_DIR);
|
|
2983
|
+
return vaultDb;
|
|
2984
|
+
}
|
|
2985
|
+
function normalizeVaultKey(key) {
|
|
2986
|
+
return key.trim().toUpperCase();
|
|
2987
|
+
}
|
|
2988
|
+
function validateVaultKey(key) {
|
|
2989
|
+
return VAULT_KEY_PATTERN.test(normalizeVaultKey(key));
|
|
2990
|
+
}
|
|
2991
|
+
function decryptRow(userId, key, storedValue) {
|
|
2992
|
+
const database = activeVaultDatabase();
|
|
2993
|
+
const decoded = decryptVaultValue(userId, key, storedValue, vaultMasterKey);
|
|
2994
|
+
if (decoded.legacyPlaintext) {
|
|
2995
|
+
database.prepare("UPDATE vault SET value = ? WHERE user_id = ? AND key = ? AND value = ?").run(encryptVaultValue(userId, key, decoded.value, vaultMasterKey), userId, key, storedValue);
|
|
2996
|
+
}
|
|
2997
|
+
return decoded.value;
|
|
2998
|
+
}
|
|
2999
|
+
function vaultListWithValues(userId) {
|
|
3000
|
+
const rows = activeVaultDatabase().prepare("SELECT key, description, value FROM vault WHERE user_id = ? ORDER BY key").all(userId);
|
|
3001
|
+
return rows.map((row) => ({
|
|
3002
|
+
key: row.key,
|
|
3003
|
+
description: row.description,
|
|
3004
|
+
value: decryptRow(userId, row.key, row.value)
|
|
3005
|
+
}));
|
|
3006
|
+
}
|
|
3007
|
+
function vaultSet(userId, key, value, description = "") {
|
|
3008
|
+
const normalizedKey = normalizeVaultKey(key);
|
|
3009
|
+
activeVaultDatabase().prepare(`INSERT INTO vault (user_id, key, value, description) VALUES (?, ?, ?, ?)
|
|
3010
|
+
ON CONFLICT (user_id, key) DO UPDATE
|
|
3011
|
+
SET value = excluded.value, description = excluded.description`).run(userId, normalizedKey, encryptVaultValue(userId, normalizedKey, value, vaultMasterKey), description);
|
|
3012
|
+
}
|
|
3013
|
+
function vaultHasKey(userId, key) {
|
|
3014
|
+
const row = activeVaultDatabase().prepare("SELECT 1 FROM vault WHERE user_id = ? AND key = ?").get(userId, normalizeVaultKey(key));
|
|
3015
|
+
return row != null;
|
|
3016
|
+
}
|
|
3017
|
+
function vaultDel(userId, key) {
|
|
3018
|
+
const result = activeVaultDatabase().prepare("DELETE FROM vault WHERE user_id = ? AND key = ?").run(userId, normalizeVaultKey(key));
|
|
3019
|
+
return result.changes > 0;
|
|
3020
|
+
}
|
|
3021
|
+
function vaultList(userId) {
|
|
3022
|
+
return activeVaultDatabase().prepare("SELECT key, description FROM vault WHERE user_id = ? ORDER BY key").all(userId);
|
|
3023
|
+
}
|
|
3024
|
+
function vaultSubstituteDetailed(userId, text) {
|
|
3025
|
+
const entries = new Map(vaultListWithValues(userId).map((entry) => [entry.key, entry.value]));
|
|
3026
|
+
const usedKeys = new Set;
|
|
3027
|
+
const substituted = text.replace(/\{\{([^}]+)\}\}/g, (match, rawKey) => {
|
|
3028
|
+
const key = normalizeVaultKey(rawKey);
|
|
3029
|
+
const value = entries.get(key);
|
|
3030
|
+
if (value === undefined)
|
|
3031
|
+
return match;
|
|
3032
|
+
usedKeys.add(key);
|
|
3033
|
+
return value;
|
|
3034
|
+
});
|
|
3035
|
+
return { text: substituted, usedKeys: [...usedKeys] };
|
|
3036
|
+
}
|
|
3037
|
+
function encodedSecretForms(value) {
|
|
3038
|
+
const forms = new Set([
|
|
3039
|
+
value,
|
|
3040
|
+
encodeURIComponent(value),
|
|
3041
|
+
Buffer.from(value, "utf8").toString("base64"),
|
|
3042
|
+
Buffer.from(value, "utf8").toString("base64url"),
|
|
3043
|
+
Buffer.from(value, "utf8").toString("hex")
|
|
3044
|
+
]);
|
|
3045
|
+
forms.delete("");
|
|
3046
|
+
return [...forms].sort((a, b) => b.length - a.length);
|
|
3047
|
+
}
|
|
3048
|
+
function redactVaultSecrets(userId, text) {
|
|
3049
|
+
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));
|
|
3050
|
+
if (candidates.length === 0)
|
|
3051
|
+
return text;
|
|
3052
|
+
const candidatesByFirstCharacter = new Map;
|
|
3053
|
+
for (const candidate of candidates) {
|
|
3054
|
+
const first = candidate.form[0];
|
|
3055
|
+
if (!first)
|
|
3056
|
+
continue;
|
|
3057
|
+
const bucket = candidatesByFirstCharacter.get(first) ?? [];
|
|
3058
|
+
bucket.push(candidate);
|
|
3059
|
+
candidatesByFirstCharacter.set(first, bucket);
|
|
3060
|
+
}
|
|
3061
|
+
let redacted = "";
|
|
3062
|
+
let offset = 0;
|
|
3063
|
+
while (offset < text.length) {
|
|
3064
|
+
const match = candidatesByFirstCharacter.get(text[offset] ?? "")?.find((candidate) => text.startsWith(candidate.form, offset));
|
|
3065
|
+
if (!match) {
|
|
3066
|
+
redacted += text[offset];
|
|
3067
|
+
offset += 1;
|
|
3068
|
+
continue;
|
|
3069
|
+
}
|
|
3070
|
+
redacted += `[REDACTED:${match.key}]`;
|
|
3071
|
+
offset += match.form.length;
|
|
3072
|
+
}
|
|
3073
|
+
return redacted;
|
|
3074
|
+
}
|
|
3075
|
+
var vaultDb, vaultMasterKey, VAULT_KEY_PATTERN, VAULT_VALUE_MIN_BYTES = 4, VAULT_VALUE_MAX_BYTES, VAULT_DESCRIPTION_MAX_LENGTH = 500;
|
|
3076
|
+
var init_vault = __esm(async () => {
|
|
3077
|
+
init_config();
|
|
3078
|
+
await init_sqlite();
|
|
3079
|
+
init_vault_crypto();
|
|
3080
|
+
vaultMasterKey = VAULT_MASTER_KEY;
|
|
3081
|
+
VAULT_KEY_PATTERN = /^[A-Z][A-Z0-9_]{0,127}$/;
|
|
3082
|
+
VAULT_VALUE_MAX_BYTES = 64 * 1024;
|
|
3083
|
+
});
|
|
3084
|
+
|
|
3091
3085
|
// ../../packages/core/src/agents/execution-host.ts
|
|
3092
3086
|
import { AsyncLocalStorage } from "async_hooks";
|
|
3093
3087
|
function activeHost() {
|
|
@@ -3119,7 +3113,7 @@ function hostedCodexAuthFilePath() {
|
|
|
3119
3113
|
}
|
|
3120
3114
|
var defaultHost, hostRegistrations, scopedHost;
|
|
3121
3115
|
var init_execution_host = __esm(async () => {
|
|
3122
|
-
|
|
3116
|
+
init_vault_tool_policy();
|
|
3123
3117
|
init_config();
|
|
3124
3118
|
init_mcp_config();
|
|
3125
3119
|
await init_vault();
|
|
@@ -3235,7 +3229,9 @@ var DEFAULT_CONTEXT_WARNING_RATIO = 0.8;
|
|
|
3235
3229
|
import { spawn as spawn2 } from "child_process";
|
|
3236
3230
|
import { existsSync as existsSync8, readFileSync as readFileSync6 } from "fs";
|
|
3237
3231
|
import { query } from "@anthropic-ai/claude-agent-sdk";
|
|
3238
|
-
function substituteClaudeToolInput(userId, input) {
|
|
3232
|
+
function substituteClaudeToolInput(userId, toolName, input) {
|
|
3233
|
+
if (!shouldSubstituteVaultToolInput(toolName))
|
|
3234
|
+
return input;
|
|
3239
3235
|
return deepMapStrings(input, (value) => substituteHostedSecrets(userId, value));
|
|
3240
3236
|
}
|
|
3241
3237
|
function buildClaudeDisallowedTools(extra = undefined) {
|
|
@@ -3468,7 +3464,7 @@ async function* claudeProvider(opts) {
|
|
|
3468
3464
|
};
|
|
3469
3465
|
}
|
|
3470
3466
|
const userId = opts.userId ?? "";
|
|
3471
|
-
const withVault = substituteClaudeToolInput(userId, tool_input);
|
|
3467
|
+
const withVault = substituteClaudeToolInput(userId, tool_name, tool_input);
|
|
3472
3468
|
if (JSON.stringify(withVault) === JSON.stringify(tool_input)) {
|
|
3473
3469
|
return { continue: true };
|
|
3474
3470
|
}
|
|
@@ -3685,6 +3681,7 @@ var CLAUDE_DEFAULT_DISALLOWED_TOOLS, CLAUDE_NATIVE_AGENT_TOOLS, CLAUDE_IMAGE_MAX
|
|
|
3685
3681
|
var init_claude_provider = __esm(async () => {
|
|
3686
3682
|
init_claude_registry();
|
|
3687
3683
|
await init_execution_host();
|
|
3684
|
+
init_vault_tool_policy();
|
|
3688
3685
|
init_file_events();
|
|
3689
3686
|
init_logger();
|
|
3690
3687
|
CLAUDE_DEFAULT_DISALLOWED_TOOLS = [
|
|
@@ -3707,7 +3704,7 @@ var init_claude_provider = __esm(async () => {
|
|
|
3707
3704
|
});
|
|
3708
3705
|
|
|
3709
3706
|
// ../../packages/core/src/version.ts
|
|
3710
|
-
var NEGOTIUM_VERSION = "0.1.
|
|
3707
|
+
var NEGOTIUM_VERSION = "0.1.23";
|
|
3711
3708
|
|
|
3712
3709
|
// ../../packages/core/src/agents/codex-native-multi-agent.ts
|
|
3713
3710
|
import { spawn as spawn3 } from "child_process";
|
|
@@ -4345,10 +4342,12 @@ function buildMaestroDisallowedTools(callerDisallowedTools = []) {
|
|
|
4345
4342
|
function buildVaultHook(userId) {
|
|
4346
4343
|
return {
|
|
4347
4344
|
name: "vault-guard",
|
|
4348
|
-
pre({ input }) {
|
|
4345
|
+
pre({ toolName, input }) {
|
|
4349
4346
|
if (referencesHostedSecretStorage(input)) {
|
|
4350
4347
|
return { decision: "block", error: "Runtime secret storage access is not permitted" };
|
|
4351
4348
|
}
|
|
4349
|
+
if (!shouldSubstituteVaultToolInput(toolName))
|
|
4350
|
+
return { decision: "allow" };
|
|
4352
4351
|
const substituted = deepMapStrings(input, (value) => substituteHostedSecrets(userId, value));
|
|
4353
4352
|
return JSON.stringify(substituted) === JSON.stringify(input) ? { decision: "allow" } : { decision: "modify", input: substituted };
|
|
4354
4353
|
},
|
|
@@ -4406,6 +4405,7 @@ var MAESTRO_DEFAULT_MAX_TOKENS = 32768, PROVIDER_ASK_USER_TOOL = "AskUserQuestio
|
|
|
4406
4405
|
var init_maestro_provider = __esm(async () => {
|
|
4407
4406
|
init_maestro_bootstrap_env();
|
|
4408
4407
|
await init_execution_host();
|
|
4408
|
+
init_vault_tool_policy();
|
|
4409
4409
|
MAESTRO_NATIVE_TASK_TOOLS = [
|
|
4410
4410
|
"TaskCreate",
|
|
4411
4411
|
"TaskUpdate",
|
|
@@ -6697,47 +6697,186 @@ function getBrowserProfileOwner(topicId, fallbackUserId) {
|
|
|
6697
6697
|
function isTopicBrowserProfileOwner(topicId, userId) {
|
|
6698
6698
|
return getBrowserProfileOwner(topicId, "") === userId;
|
|
6699
6699
|
}
|
|
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;
|
|
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;
|
|
6703
|
+
}
|
|
6704
|
+
function hasBrowserProfileTopic(topicId) {
|
|
6705
|
+
return Boolean(db.query("SELECT id FROM api_topics WHERE id = ?").get(topicId));
|
|
6706
|
+
}
|
|
6707
|
+
function createBrowserProfile(ownerId, rawName) {
|
|
6708
|
+
const name = normalizeBrowserProfileName(rawName);
|
|
6709
|
+
if (name === DEFAULT_BROWSER_PROFILE)
|
|
6710
|
+
return name;
|
|
6711
|
+
db.query("INSERT OR IGNORE INTO browser_profiles (owner_id, name, created_at) VALUES (?, ?, ?)").run(ownerId, name, new Date().toISOString());
|
|
6712
|
+
return name;
|
|
6713
|
+
}
|
|
6714
|
+
function assignTopicBrowserProfile(opts) {
|
|
6715
|
+
const actualOwner = getBrowserProfileOwner(opts.topicId, "");
|
|
6716
|
+
if (!actualOwner)
|
|
6717
|
+
throw new Error(`Topic "${opts.topicId}" not found or has no owner.`);
|
|
6718
|
+
if (actualOwner !== opts.actorUserId) {
|
|
6719
|
+
throw new Error(`Only the topic owner can change its browser profile.`);
|
|
6720
|
+
}
|
|
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
|
+
}
|
|
6730
|
+
var DEFAULT_BROWSER_PROFILE = "default";
|
|
6731
|
+
var init_browser_profiles = __esm(async () => {
|
|
6732
|
+
await init_forum_db();
|
|
6733
|
+
db.exec(`
|
|
6734
|
+
CREATE TABLE IF NOT EXISTS browser_profiles (
|
|
6735
|
+
owner_id TEXT NOT NULL,
|
|
6736
|
+
name TEXT NOT NULL,
|
|
6737
|
+
created_at TEXT NOT NULL,
|
|
6738
|
+
PRIMARY KEY (owner_id, name)
|
|
6739
|
+
)
|
|
6740
|
+
`);
|
|
6741
|
+
});
|
|
6742
|
+
|
|
6743
|
+
// ../../packages/core/src/storage/runtime-process-leases.ts
|
|
6744
|
+
function rowToLease2(row) {
|
|
6745
|
+
return {
|
|
6746
|
+
role: row.role,
|
|
6747
|
+
ownerId: row.owner_id,
|
|
6748
|
+
pid: Number(row.pid),
|
|
6749
|
+
startedAt: Number(row.started_at),
|
|
6750
|
+
heartbeatAt: Number(row.heartbeat_at)
|
|
6751
|
+
};
|
|
6752
|
+
}
|
|
6753
|
+
function isProcessAlive(pid) {
|
|
6754
|
+
if (!Number.isSafeInteger(pid) || pid <= 0)
|
|
6755
|
+
return false;
|
|
6756
|
+
try {
|
|
6757
|
+
process.kill(pid, 0);
|
|
6758
|
+
return true;
|
|
6759
|
+
} catch (error) {
|
|
6760
|
+
return error.code !== "ESRCH";
|
|
6761
|
+
}
|
|
6762
|
+
}
|
|
6763
|
+
function removeDeadRuntimeProcessLease(role) {
|
|
6764
|
+
const row = db.query("SELECT * FROM runtime_process_leases WHERE role = ?").get(role);
|
|
6765
|
+
if (!row)
|
|
6766
|
+
return;
|
|
6767
|
+
const lease = rowToLease2(row);
|
|
6768
|
+
if (isProcessAlive(lease.pid))
|
|
6769
|
+
return;
|
|
6770
|
+
db.query(`DELETE FROM runtime_process_leases
|
|
6771
|
+
WHERE role = ? AND owner_id = ? AND pid = ? AND heartbeat_at = ?`).run(lease.role, lease.ownerId, lease.pid, lease.heartbeatAt);
|
|
6772
|
+
}
|
|
6773
|
+
function getRuntimeProcessLease(role, now = Date.now(), staleMs = PROCESS_LEASE_STALE_MS) {
|
|
6774
|
+
const row = db.query("SELECT * FROM runtime_process_leases WHERE role = ?").get(role);
|
|
6775
|
+
if (!row)
|
|
6776
|
+
return null;
|
|
6777
|
+
const lease = rowToLease2(row);
|
|
6778
|
+
return now - lease.heartbeatAt <= staleMs ? lease : null;
|
|
6779
|
+
}
|
|
6780
|
+
function listRuntimeProcessLeases(rolePrefix = "", now = Date.now(), staleMs = PROCESS_LEASE_STALE_MS) {
|
|
6781
|
+
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();
|
|
6782
|
+
return rows.map(rowToLease2).filter((lease) => now - lease.heartbeatAt <= staleMs);
|
|
6783
|
+
}
|
|
6784
|
+
function heartbeatRuntimeProcessLease(role, ownerId, now = Date.now()) {
|
|
6785
|
+
const result = db.query(`UPDATE runtime_process_leases
|
|
6786
|
+
SET heartbeat_at = ?
|
|
6787
|
+
WHERE role = ? AND owner_id = ?`).run(now, role, ownerId);
|
|
6788
|
+
return Number(result.changes ?? 0) > 0;
|
|
6703
6789
|
}
|
|
6704
|
-
function
|
|
6705
|
-
|
|
6790
|
+
function releaseRuntimeProcessLease(role, ownerId) {
|
|
6791
|
+
const result = db.query("DELETE FROM runtime_process_leases WHERE role = ? AND owner_id = ?").run(role, ownerId);
|
|
6792
|
+
return Number(result.changes ?? 0) > 0;
|
|
6706
6793
|
}
|
|
6707
|
-
function
|
|
6708
|
-
const
|
|
6709
|
-
if (
|
|
6710
|
-
|
|
6711
|
-
|
|
6712
|
-
|
|
6794
|
+
function acquireRuntimeProcessLease(role, options = {}) {
|
|
6795
|
+
const normalizedRole = role.trim();
|
|
6796
|
+
if (!normalizedRole)
|
|
6797
|
+
throw new Error("runtime process lease role must not be empty");
|
|
6798
|
+
removeDeadRuntimeProcessLease(normalizedRole);
|
|
6799
|
+
const ownerId = options.ownerId ?? `${RUNTIME_INSTANCE_ID}:${normalizedRole}`;
|
|
6800
|
+
const pid = options.pid ?? process.pid;
|
|
6801
|
+
const now = options.now ?? Date.now();
|
|
6802
|
+
const staleMs = options.staleMs ?? PROCESS_LEASE_STALE_MS;
|
|
6803
|
+
const result = db.query(`INSERT INTO runtime_process_leases (role, owner_id, pid, started_at, heartbeat_at)
|
|
6804
|
+
VALUES (?, ?, ?, ?, ?)
|
|
6805
|
+
ON CONFLICT(role) DO UPDATE SET
|
|
6806
|
+
owner_id = excluded.owner_id,
|
|
6807
|
+
pid = excluded.pid,
|
|
6808
|
+
started_at = excluded.started_at,
|
|
6809
|
+
heartbeat_at = excluded.heartbeat_at
|
|
6810
|
+
WHERE runtime_process_leases.heartbeat_at < ?`).run(normalizedRole, ownerId, pid, now, now, now - staleMs);
|
|
6811
|
+
if (Number(result.changes ?? 0) === 0)
|
|
6812
|
+
return null;
|
|
6813
|
+
let stopped = false;
|
|
6814
|
+
const heartbeatMs = options.heartbeatMs ?? PROCESS_LEASE_HEARTBEAT_MS;
|
|
6815
|
+
const timer = setInterval(() => {
|
|
6816
|
+
if (stopped)
|
|
6817
|
+
return;
|
|
6818
|
+
if (heartbeatRuntimeProcessLease(normalizedRole, ownerId))
|
|
6819
|
+
return;
|
|
6820
|
+
stopped = true;
|
|
6821
|
+
clearInterval(timer);
|
|
6822
|
+
options.onLost?.();
|
|
6823
|
+
}, heartbeatMs);
|
|
6824
|
+
timer.unref?.();
|
|
6825
|
+
return {
|
|
6826
|
+
role: normalizedRole,
|
|
6827
|
+
ownerId,
|
|
6828
|
+
pid,
|
|
6829
|
+
startedAt: now,
|
|
6830
|
+
heartbeatAt: now,
|
|
6831
|
+
stop() {
|
|
6832
|
+
if (stopped)
|
|
6833
|
+
return;
|
|
6834
|
+
stopped = true;
|
|
6835
|
+
clearInterval(timer);
|
|
6836
|
+
releaseRuntimeProcessLease(normalizedRole, ownerId);
|
|
6837
|
+
}
|
|
6838
|
+
};
|
|
6713
6839
|
}
|
|
6714
|
-
function
|
|
6715
|
-
const
|
|
6716
|
-
|
|
6717
|
-
|
|
6718
|
-
|
|
6719
|
-
|
|
6840
|
+
async function waitForRuntimeProcessLease(role, options = {}) {
|
|
6841
|
+
const {
|
|
6842
|
+
waitMs = PROCESS_LEASE_STALE_MS + PROCESS_LEASE_HEARTBEAT_MS,
|
|
6843
|
+
retryMs = 100,
|
|
6844
|
+
...acquireOptions
|
|
6845
|
+
} = options;
|
|
6846
|
+
const deadline = Date.now() + Math.max(0, waitMs);
|
|
6847
|
+
const interval = Math.max(1, retryMs);
|
|
6848
|
+
while (true) {
|
|
6849
|
+
const lease = acquireRuntimeProcessLease(role, acquireOptions);
|
|
6850
|
+
if (lease)
|
|
6851
|
+
return lease;
|
|
6852
|
+
const remaining = deadline - Date.now();
|
|
6853
|
+
if (remaining <= 0)
|
|
6854
|
+
return null;
|
|
6855
|
+
await new Promise((resolve5) => setTimeout(resolve5, Math.min(interval, remaining)));
|
|
6720
6856
|
}
|
|
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
6857
|
}
|
|
6730
|
-
|
|
6731
|
-
|
|
6858
|
+
async function waitForRequiredRuntimeProcessLease(role, options = {}) {
|
|
6859
|
+
const { workloadName = role, ...leaseOptions } = options;
|
|
6860
|
+
const lease = await waitForRuntimeProcessLease(role, leaseOptions);
|
|
6861
|
+
if (lease)
|
|
6862
|
+
return lease;
|
|
6863
|
+
const current2 = getRuntimeProcessLease(role);
|
|
6864
|
+
throw new Error(`${workloadName} is already running${current2 ? ` (pid ${current2.pid})` : ""}`);
|
|
6865
|
+
}
|
|
6866
|
+
var PROCESS_LEASE_STALE_MS = 5000, PROCESS_LEASE_HEARTBEAT_MS = 1000;
|
|
6867
|
+
var init_runtime_process_leases = __esm(async () => {
|
|
6732
6868
|
await init_forum_db();
|
|
6869
|
+
await init_runtime_leases();
|
|
6733
6870
|
db.exec(`
|
|
6734
|
-
CREATE TABLE IF NOT EXISTS
|
|
6735
|
-
|
|
6736
|
-
|
|
6737
|
-
|
|
6738
|
-
|
|
6871
|
+
CREATE TABLE IF NOT EXISTS runtime_process_leases (
|
|
6872
|
+
role TEXT PRIMARY KEY,
|
|
6873
|
+
owner_id TEXT NOT NULL UNIQUE,
|
|
6874
|
+
pid INTEGER NOT NULL,
|
|
6875
|
+
started_at INTEGER NOT NULL,
|
|
6876
|
+
heartbeat_at INTEGER NOT NULL
|
|
6739
6877
|
)
|
|
6740
6878
|
`);
|
|
6879
|
+
db.exec("CREATE INDEX IF NOT EXISTS idx_runtime_process_leases_heartbeat ON runtime_process_leases(heartbeat_at)");
|
|
6741
6880
|
});
|
|
6742
6881
|
|
|
6743
6882
|
// ../../packages/core/src/platform/playwright/manager.ts
|
|
@@ -7026,7 +7165,13 @@ function selectOrphanBrowserPids(procs, liveUserDataDirs, profileRoot, selfPid)
|
|
|
7026
7165
|
}
|
|
7027
7166
|
return out;
|
|
7028
7167
|
}
|
|
7168
|
+
function isBrowserJanitorOwner(leaseOwnerPid, selfPid) {
|
|
7169
|
+
return leaseOwnerPid === selfPid;
|
|
7170
|
+
}
|
|
7029
7171
|
function reapOrphanBrowsers() {
|
|
7172
|
+
const daemonLease = getRuntimeProcessLease("node-daemon", Date.now(), Number.POSITIVE_INFINITY);
|
|
7173
|
+
if (!isBrowserJanitorOwner(daemonLease?.pid ?? null, process.pid))
|
|
7174
|
+
return;
|
|
7030
7175
|
const profileRoot = resolve5(BROWSER_PROFILES_DIR);
|
|
7031
7176
|
let pids;
|
|
7032
7177
|
try {
|
|
@@ -7437,6 +7582,7 @@ var init_manager2 = __esm(async () => {
|
|
|
7437
7582
|
init_config();
|
|
7438
7583
|
init_logger();
|
|
7439
7584
|
await init_browser_profiles();
|
|
7585
|
+
await init_runtime_process_leases();
|
|
7440
7586
|
BASE_PORT = PLAYWRIGHT_BASE_PORT;
|
|
7441
7587
|
MAX_PORT = PLAYWRIGHT_MAX_PORT;
|
|
7442
7588
|
MAX_IDLE_MS = 2 * 60 * 60 * 1000;
|
|
@@ -15454,145 +15600,6 @@ var init_lifecycle2 = __esm(() => {
|
|
|
15454
15600
|
runShutdown = defaultLifecycle.runShutdown;
|
|
15455
15601
|
});
|
|
15456
15602
|
|
|
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
15603
|
// ../../packages/core/src/platform/modules.ts
|
|
15597
15604
|
function startNegotiumNodeModules(modules, context) {
|
|
15598
15605
|
const seen = new Set;
|
|
@@ -20926,6 +20933,8 @@ async function runNodeDaemon(opts = {}) {
|
|
|
20926
20933
|
singleton: true
|
|
20927
20934
|
});
|
|
20928
20935
|
await node.completed;
|
|
20936
|
+
await new Promise((resolve13) => setImmediate(resolve13));
|
|
20937
|
+
process.exit(0);
|
|
20929
20938
|
}
|
|
20930
20939
|
var init_src5 = __esm(async () => {
|
|
20931
20940
|
await init_src();
|
|
@@ -32761,6 +32770,8 @@ async function runCanonicalNode(port) {
|
|
|
32761
32770
|
});
|
|
32762
32771
|
console.log(`negotium node listening on 127.0.0.1:${node.port} (ctrl-c to stop)`);
|
|
32763
32772
|
await node.completed;
|
|
32773
|
+
await new Promise((resolve15) => setImmediate(resolve15));
|
|
32774
|
+
process.exit(0);
|
|
32764
32775
|
}
|
|
32765
32776
|
async function stopAdapter(name) {
|
|
32766
32777
|
const { getRuntimeProcessLease: getRuntimeProcessLease2 } = await init_src().then(() => exports_src);
|
|
@@ -32889,4 +32900,4 @@ switch (command) {
|
|
|
32889
32900
|
}
|
|
32890
32901
|
}
|
|
32891
32902
|
|
|
32892
|
-
//# debugId=
|
|
32903
|
+
//# debugId=96E61A2450BA103464756E2164756E21
|