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/main.js CHANGED
@@ -1958,220 +1958,15 @@ 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);
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 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;
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 !isVaultBrokerTool(toolName) && host.valueReferencesVaultKey(userId, input);
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(async () => {
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
- defaultVaultToolPolicy = createVaultToolPolicy({ isSensitivePath, valueReferencesVaultKey });
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 randomBytes4 } from "crypto";
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 ?? randomBytes4(32).toString("hex");
2387
- const runtimeServerId = options.serverId ?? randomBytes4(16).toString("hex");
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}`, "--list-only=true"];
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
- await init_vault_tool_policy();
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.21";
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 hasBrowserProfileTopic(topicId) {
6705
- return Boolean(db.query("SELECT id FROM api_topics WHERE id = ?").get(topicId));
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 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;
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 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.`);
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
- var DEFAULT_BROWSER_PROFILE = "default";
6731
- var init_browser_profiles = __esm(async () => {
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 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)
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=C8F4C2CD3CFA25F564756E2164756E21
32903
+ //# debugId=96E61A2450BA103464756E2164756E21