negotium 0.1.21 → 0.1.22

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