negotium 0.1.28 → 0.1.29
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 +539 -389
- package/dist/agent-helpers.js.map +14 -14
- package/dist/background-bash.js +60 -5
- package/dist/background-bash.js.map +3 -3
- package/dist/{chunk-7azk83mw.js → chunk-yk55n9dk.js} +58 -3
- package/dist/{chunk-7azk83mw.js.map → chunk-yk55n9dk.js.map} +3 -3
- package/dist/cron.js +555 -406
- package/dist/cron.js.map +19 -19
- package/dist/hosted-agent.js +201 -55
- package/dist/hosted-agent.js.map +9 -9
- package/dist/main.js +952 -530
- package/dist/main.js.map +22 -22
- package/dist/mcp-factories.js +58 -3
- package/dist/mcp-factories.js.map +3 -3
- package/dist/prompts.js +58 -3
- package/dist/prompts.js.map +3 -3
- package/dist/registry.js +1 -1
- package/dist/rollout.js +1 -1
- package/dist/runtime/scripts/browser-passkey-policy.mjs +15 -4
- package/dist/runtime/scripts/browser-vault-transform.mjs +5 -1
- package/dist/runtime/scripts/mcp-patchright-http.mjs +241 -26
- package/dist/runtime/src/agents/api-topic-agent-switch.ts +1 -1
- package/dist/runtime/src/agents/auth-check.ts +36 -10
- package/dist/runtime/src/agents/claude-provider.ts +4 -11
- package/dist/runtime/src/agents/codex-native-multi-agent.ts +159 -42
- package/dist/runtime/src/agents/codex-provider.ts +6 -3
- package/dist/runtime/src/agents/maestro-provider.ts +26 -0
- package/dist/runtime/src/agents/mcp-tools/spawn-subagent.ts +1 -1
- package/dist/runtime/src/agents/self-config-core.ts +1 -1
- package/dist/runtime/src/agents/topic-agent-switch.ts +1 -1
- package/dist/runtime/src/application/switch-topic-model.ts +1 -1
- package/dist/runtime/src/mcp/browser-sse-proxy-server.ts +48 -0
- package/dist/runtime/src/platform/config.ts +80 -4
- package/dist/runtime/src/platform/mcp-config.ts +13 -9
- package/dist/runtime/src/platform/playwright/manager.ts +24 -14
- package/dist/runtime/src/version.ts +1 -1
- package/dist/runtime-helpers.js +58 -3
- package/dist/runtime-helpers.js.map +3 -3
- package/dist/types/packages/core/src/agents/auth-check.d.ts +3 -2
- package/dist/types/packages/core/src/agents/codex-native-multi-agent.d.ts +6 -4
- package/dist/types/packages/core/src/agents/maestro-provider.d.ts +13 -0
- package/dist/types/packages/core/src/platform/config.d.ts +13 -1
- package/dist/types/packages/core/src/version.d.ts +1 -1
- package/dist/vault.js +58 -3
- package/dist/vault.js.map +3 -3
- package/install-browser-rs.mjs +77 -0
- package/package.json +5 -3
package/dist/agent-helpers.js
CHANGED
|
@@ -18,14 +18,23 @@ var __toESM = (mod, isNodeMode, target) => {
|
|
|
18
18
|
var __require = import.meta.require;
|
|
19
19
|
|
|
20
20
|
// ../../packages/core/src/agents/auth-check.ts
|
|
21
|
-
import { execFileSync } from "child_process";
|
|
21
|
+
import { execFileSync as execFileSync2 } from "child_process";
|
|
22
22
|
import { existsSync as existsSync2 } from "fs";
|
|
23
23
|
import { homedir as homedir2, platform } from "os";
|
|
24
|
-
import { join as
|
|
24
|
+
import { join as join3 } from "path";
|
|
25
25
|
|
|
26
26
|
// ../../packages/core/src/platform/config.ts
|
|
27
|
+
import { execFileSync } from "child_process";
|
|
27
28
|
import { randomBytes } from "crypto";
|
|
28
|
-
import {
|
|
29
|
+
import {
|
|
30
|
+
accessSync,
|
|
31
|
+
chmodSync,
|
|
32
|
+
constants,
|
|
33
|
+
existsSync,
|
|
34
|
+
mkdirSync,
|
|
35
|
+
readFileSync,
|
|
36
|
+
writeFileSync
|
|
37
|
+
} from "fs";
|
|
29
38
|
import { homedir } from "os";
|
|
30
39
|
import { dirname, join, resolve } from "path";
|
|
31
40
|
import { fileURLToPath } from "url";
|
|
@@ -119,7 +128,8 @@ var CONTEXTS_DIR = resolve(WORKSPACE_DIR, "contexts");
|
|
|
119
128
|
var BROWSER_PROFILES_DIR = resolve(WORKSPACE_DIR, "browser-profiles");
|
|
120
129
|
var DM_WORKSPACE_DIR = resolve(WORKSPACE_DIR, "dm");
|
|
121
130
|
var SESSION_WORKSPACE_DIR = resolve(WORKSPACE_DIR, "sessions");
|
|
122
|
-
var
|
|
131
|
+
var CLAUDE_EXECUTABLE_ENV = envText("NEGOTIUM_CLAUDE_EXECUTABLE");
|
|
132
|
+
var CLAUDE_EXECUTABLE = CLAUDE_EXECUTABLE_ENV ? resolve(CLAUDE_EXECUTABLE_ENV) : undefined;
|
|
123
133
|
function resolveBrowserMcpBin(envValue) {
|
|
124
134
|
const override = envValue?.trim();
|
|
125
135
|
if (override) {
|
|
@@ -132,10 +142,55 @@ function resolveBrowserMcpBin(envValue) {
|
|
|
132
142
|
}
|
|
133
143
|
var PATCHRIGHT_MCP_BIN = resolve(PROJECT_ROOT, "scripts/mcp-patchright-http.mjs");
|
|
134
144
|
var PLAYWRIGHT_MCP_BIN = resolveBrowserMcpBin(envText("NEGOTIUM_BROWSER_MCP_BIN"));
|
|
145
|
+
var BROWSER_RS_VERSION = "v0.1.12";
|
|
146
|
+
var BROWSER_RS_MIN_SECURE_VERSION = "0.1.12";
|
|
147
|
+
function versionAtLeast(actualVersion, minimumVersion) {
|
|
148
|
+
const actual = actualVersion.split(".").map(Number);
|
|
149
|
+
const minimum = minimumVersion.split(".").map(Number);
|
|
150
|
+
if (actual.some(Number.isNaN) || minimum.some(Number.isNaN))
|
|
151
|
+
return false;
|
|
152
|
+
for (let index = 0;index < minimum.length; index += 1) {
|
|
153
|
+
if ((actual[index] ?? 0) > (minimum[index] ?? 0))
|
|
154
|
+
return true;
|
|
155
|
+
if ((actual[index] ?? 0) < (minimum[index] ?? 0))
|
|
156
|
+
return false;
|
|
157
|
+
}
|
|
158
|
+
return true;
|
|
159
|
+
}
|
|
160
|
+
function browserRsMeetsMinimumVersion(candidate) {
|
|
161
|
+
try {
|
|
162
|
+
const output = execFileSync(candidate, ["--version"], {
|
|
163
|
+
encoding: "utf8",
|
|
164
|
+
timeout: 2000,
|
|
165
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
166
|
+
}).trim();
|
|
167
|
+
const match = output.match(/^browser-rs (\d+)\.(\d+)\.(\d+)$/);
|
|
168
|
+
if (!match)
|
|
169
|
+
return false;
|
|
170
|
+
return versionAtLeast(match.slice(1).join("."), BROWSER_RS_MIN_SECURE_VERSION);
|
|
171
|
+
} catch {
|
|
172
|
+
return false;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
function resolveBrowserRsBin(envValue) {
|
|
176
|
+
const override = envValue?.trim();
|
|
177
|
+
if (!override && !versionAtLeast(BROWSER_RS_VERSION.replace(/^v/, ""), BROWSER_RS_MIN_SECURE_VERSION)) {
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
const candidate = override ? resolve(override) : resolve(STATE_DIR, "bin", "browser-rs", BROWSER_RS_VERSION, "browser-rs");
|
|
181
|
+
try {
|
|
182
|
+
accessSync(candidate, constants.X_OK);
|
|
183
|
+
return browserRsMeetsMinimumVersion(candidate) ? candidate : undefined;
|
|
184
|
+
} catch {
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
var BROWSER_RS_BIN = resolveBrowserRsBin(envText("NEGOTIUM_BROWSER_RS_BIN"));
|
|
135
189
|
var TSX_BIN = resolveDependencyBin("tsx");
|
|
136
190
|
var TSCONFIG_PATH = resolve(PROJECT_ROOT, "tsconfig.json");
|
|
137
191
|
var SESSION_COMM_SERVER = resolve(PROJECT_ROOT, "src/mcp/session-comm/server.ts");
|
|
138
192
|
var TASK_SERVER = resolve(PROJECT_ROOT, "src/mcp/task-server.ts");
|
|
193
|
+
var BROWSER_MCP_SSE_PROXY_SERVER = resolve(PROJECT_ROOT, "src/mcp/browser-sse-proxy-server.ts");
|
|
139
194
|
var CANONICAL_MCP_PROXY_SERVER = resolve(PROJECT_ROOT, "src/mcp/canonical-proxy-server.ts");
|
|
140
195
|
var WIKI_SERVER = resolve(PROJECT_ROOT, "src/mcp/wiki-server.ts");
|
|
141
196
|
var TOKEN_STATS_SERVER = resolve(PROJECT_ROOT, "src/mcp/token-stats-server.ts");
|
|
@@ -253,6 +308,231 @@ function codexAuthFilePath() {
|
|
|
253
308
|
return process.env.NEGOTIUM_CODEX_AUTH_FILE || join(homedir(), ".codex", "auth.json");
|
|
254
309
|
}
|
|
255
310
|
|
|
311
|
+
// ../../packages/core/src/storage/vault.ts
|
|
312
|
+
import { chmodSync as chmodSync2, mkdirSync as mkdirSync2 } from "fs";
|
|
313
|
+
import { join as join2 } from "path";
|
|
314
|
+
|
|
315
|
+
// ../../packages/core/src/storage/sqlite.ts
|
|
316
|
+
var isBun = typeof process.versions.bun === "string";
|
|
317
|
+
var Database;
|
|
318
|
+
if (isBun) {
|
|
319
|
+
({ Database } = await import("bun:sqlite"));
|
|
320
|
+
} else {
|
|
321
|
+
const nodeSqliteSpecifier = ["node", "sqlite"].join(":");
|
|
322
|
+
const { DatabaseSync } = await import(nodeSqliteSpecifier);
|
|
323
|
+
|
|
324
|
+
class NodeDatabase {
|
|
325
|
+
#db;
|
|
326
|
+
constructor(path, options = {}) {
|
|
327
|
+
this.#db = options.readonly ? new DatabaseSync(path, { readOnly: true }) : new DatabaseSync(path);
|
|
328
|
+
}
|
|
329
|
+
query(sql) {
|
|
330
|
+
return this.#db.prepare(sql);
|
|
331
|
+
}
|
|
332
|
+
prepare(sql) {
|
|
333
|
+
return this.#db.prepare(sql);
|
|
334
|
+
}
|
|
335
|
+
exec(sql) {
|
|
336
|
+
this.#db.exec(sql);
|
|
337
|
+
}
|
|
338
|
+
run(sql, ...params) {
|
|
339
|
+
return this.#db.prepare(sql).run(...params);
|
|
340
|
+
}
|
|
341
|
+
transaction(fn) {
|
|
342
|
+
const run = (begin) => (...args) => {
|
|
343
|
+
this.#db.exec(begin);
|
|
344
|
+
try {
|
|
345
|
+
const result = fn(...args);
|
|
346
|
+
this.#db.exec("COMMIT");
|
|
347
|
+
return result;
|
|
348
|
+
} catch (err) {
|
|
349
|
+
this.#db.exec("ROLLBACK");
|
|
350
|
+
throw err;
|
|
351
|
+
}
|
|
352
|
+
};
|
|
353
|
+
const tx = run("BEGIN");
|
|
354
|
+
tx.deferred = run("BEGIN DEFERRED");
|
|
355
|
+
tx.immediate = run("BEGIN IMMEDIATE");
|
|
356
|
+
tx.exclusive = run("BEGIN EXCLUSIVE");
|
|
357
|
+
return tx;
|
|
358
|
+
}
|
|
359
|
+
close() {
|
|
360
|
+
this.#db.close();
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
Database = NodeDatabase;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
// ../../packages/core/src/storage/vault-crypto.ts
|
|
367
|
+
import { createCipheriv, createDecipheriv, createHash, randomBytes as randomBytes2 } from "crypto";
|
|
368
|
+
var ENVELOPE_PREFIX = "otium-vault:v1:";
|
|
369
|
+
var IV_BYTES = 12;
|
|
370
|
+
var KEY_BYTES = 32;
|
|
371
|
+
function encryptionKey(masterKey = VAULT_MASTER_KEY) {
|
|
372
|
+
return createHash("sha256").update("otium-vault-value-v1\x00", "utf8").update(masterKey, "utf8").digest().subarray(0, KEY_BYTES);
|
|
373
|
+
}
|
|
374
|
+
function aad(userId, key) {
|
|
375
|
+
return Buffer.from(`${userId}\x00${key.toUpperCase()}`, "utf8");
|
|
376
|
+
}
|
|
377
|
+
function isEncryptedVaultValue(value) {
|
|
378
|
+
return value.startsWith(ENVELOPE_PREFIX);
|
|
379
|
+
}
|
|
380
|
+
function encryptVaultValue(userId, key, value, masterKey = VAULT_MASTER_KEY) {
|
|
381
|
+
const iv = randomBytes2(IV_BYTES);
|
|
382
|
+
const cipher = createCipheriv("aes-256-gcm", encryptionKey(masterKey), iv);
|
|
383
|
+
cipher.setAAD(aad(userId, key));
|
|
384
|
+
const ciphertext = Buffer.concat([cipher.update(value, "utf8"), cipher.final()]);
|
|
385
|
+
const tag = cipher.getAuthTag();
|
|
386
|
+
return `${ENVELOPE_PREFIX}${iv.toString("base64url")}.${ciphertext.toString("base64url")}.${tag.toString("base64url")}`;
|
|
387
|
+
}
|
|
388
|
+
function decryptVaultValue(userId, key, storedValue, masterKey = VAULT_MASTER_KEY) {
|
|
389
|
+
if (!isEncryptedVaultValue(storedValue)) {
|
|
390
|
+
return { value: storedValue, legacyPlaintext: true };
|
|
391
|
+
}
|
|
392
|
+
const encoded = storedValue.slice(ENVELOPE_PREFIX.length);
|
|
393
|
+
const [ivPart, ciphertextPart, tagPart, ...extra] = encoded.split(".");
|
|
394
|
+
if (!ivPart || ciphertextPart === undefined || !tagPart || extra.length > 0) {
|
|
395
|
+
throw new Error("Invalid encrypted vault value");
|
|
396
|
+
}
|
|
397
|
+
const iv = Buffer.from(ivPart, "base64url");
|
|
398
|
+
const ciphertext = Buffer.from(ciphertextPart, "base64url");
|
|
399
|
+
const tag = Buffer.from(tagPart, "base64url");
|
|
400
|
+
if (iv.length !== IV_BYTES || tag.length !== 16) {
|
|
401
|
+
throw new Error("Invalid encrypted vault value");
|
|
402
|
+
}
|
|
403
|
+
const decipher = createDecipheriv("aes-256-gcm", encryptionKey(masterKey), iv);
|
|
404
|
+
decipher.setAAD(aad(userId, key));
|
|
405
|
+
decipher.setAuthTag(tag);
|
|
406
|
+
const plaintext = Buffer.concat([decipher.update(ciphertext), decipher.final()]);
|
|
407
|
+
return { value: plaintext.toString("utf8"), legacyPlaintext: false };
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
// ../../packages/core/src/storage/vault.ts
|
|
411
|
+
var vaultDb;
|
|
412
|
+
var vaultMasterKey = VAULT_MASTER_KEY;
|
|
413
|
+
function initializeVaultDatabase(database) {
|
|
414
|
+
database.exec("PRAGMA journal_mode = WAL");
|
|
415
|
+
database.exec("PRAGMA busy_timeout = 5000");
|
|
416
|
+
database.exec(`
|
|
417
|
+
CREATE TABLE IF NOT EXISTS vault (
|
|
418
|
+
user_id TEXT NOT NULL,
|
|
419
|
+
key TEXT NOT NULL,
|
|
420
|
+
value TEXT NOT NULL,
|
|
421
|
+
description TEXT NOT NULL DEFAULT '',
|
|
422
|
+
PRIMARY KEY (user_id, key)
|
|
423
|
+
)
|
|
424
|
+
`);
|
|
425
|
+
{
|
|
426
|
+
const cols = database.prepare("PRAGMA table_info(vault)").all();
|
|
427
|
+
const uid = cols.find((c) => c.name === "user_id");
|
|
428
|
+
if (uid && uid.type.toUpperCase() === "INTEGER") {
|
|
429
|
+
database.exec("BEGIN");
|
|
430
|
+
database.exec(`
|
|
431
|
+
CREATE TABLE vault_migrated (
|
|
432
|
+
user_id TEXT NOT NULL,
|
|
433
|
+
key TEXT NOT NULL,
|
|
434
|
+
value TEXT NOT NULL,
|
|
435
|
+
description TEXT NOT NULL DEFAULT '',
|
|
436
|
+
PRIMARY KEY (user_id, key)
|
|
437
|
+
)
|
|
438
|
+
`);
|
|
439
|
+
database.exec("INSERT INTO vault_migrated SELECT CAST(user_id AS TEXT), key, value, description FROM vault");
|
|
440
|
+
database.exec("DROP TABLE vault");
|
|
441
|
+
database.exec("ALTER TABLE vault_migrated RENAME TO vault");
|
|
442
|
+
database.exec("COMMIT");
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
function openVaultDatabase(dataDir) {
|
|
447
|
+
const path = join2(dataDir, "vault.db");
|
|
448
|
+
mkdirSync2(dataDir, { recursive: true });
|
|
449
|
+
const database = new Database(path, { create: true });
|
|
450
|
+
chmodSync2(path, 384);
|
|
451
|
+
initializeVaultDatabase(database);
|
|
452
|
+
return database;
|
|
453
|
+
}
|
|
454
|
+
function activeVaultDatabase() {
|
|
455
|
+
if (!vaultDb)
|
|
456
|
+
vaultDb = openVaultDatabase(DATA_DIR);
|
|
457
|
+
return vaultDb;
|
|
458
|
+
}
|
|
459
|
+
var VAULT_VALUE_MAX_BYTES = 64 * 1024;
|
|
460
|
+
function normalizeVaultKey(key) {
|
|
461
|
+
return key.trim().toUpperCase();
|
|
462
|
+
}
|
|
463
|
+
function decryptRow(userId, key, storedValue) {
|
|
464
|
+
const database = activeVaultDatabase();
|
|
465
|
+
const decoded = decryptVaultValue(userId, key, storedValue, vaultMasterKey);
|
|
466
|
+
if (decoded.legacyPlaintext) {
|
|
467
|
+
database.prepare("UPDATE vault SET value = ? WHERE user_id = ? AND key = ? AND value = ?").run(encryptVaultValue(userId, key, decoded.value, vaultMasterKey), userId, key, storedValue);
|
|
468
|
+
}
|
|
469
|
+
return decoded.value;
|
|
470
|
+
}
|
|
471
|
+
function vaultListWithValues(userId) {
|
|
472
|
+
const rows = activeVaultDatabase().prepare("SELECT key, description, value FROM vault WHERE user_id = ? ORDER BY key").all(userId);
|
|
473
|
+
return rows.map((row) => ({
|
|
474
|
+
key: row.key,
|
|
475
|
+
description: row.description,
|
|
476
|
+
value: decryptRow(userId, row.key, row.value)
|
|
477
|
+
}));
|
|
478
|
+
}
|
|
479
|
+
function vaultGetValue(userId, key) {
|
|
480
|
+
const normalizedKey = normalizeVaultKey(key);
|
|
481
|
+
const row = activeVaultDatabase().prepare("SELECT key, value FROM vault WHERE user_id = ? AND key = ?").get(userId, normalizedKey);
|
|
482
|
+
return row ? decryptRow(userId, row.key, row.value) : undefined;
|
|
483
|
+
}
|
|
484
|
+
function vaultSubstituteDetailed(userId, text) {
|
|
485
|
+
const entries = new Map(vaultListWithValues(userId).map((entry) => [entry.key, entry.value]));
|
|
486
|
+
const usedKeys = new Set;
|
|
487
|
+
const substituted = text.replace(/\{\{([^}]+)\}\}/g, (match, rawKey) => {
|
|
488
|
+
const key = normalizeVaultKey(rawKey);
|
|
489
|
+
const value = entries.get(key);
|
|
490
|
+
if (value === undefined)
|
|
491
|
+
return match;
|
|
492
|
+
usedKeys.add(key);
|
|
493
|
+
return value;
|
|
494
|
+
});
|
|
495
|
+
return { text: substituted, usedKeys: [...usedKeys] };
|
|
496
|
+
}
|
|
497
|
+
function encodedSecretForms(value) {
|
|
498
|
+
const forms = new Set([
|
|
499
|
+
value,
|
|
500
|
+
encodeURIComponent(value),
|
|
501
|
+
Buffer.from(value, "utf8").toString("base64"),
|
|
502
|
+
Buffer.from(value, "utf8").toString("base64url"),
|
|
503
|
+
Buffer.from(value, "utf8").toString("hex")
|
|
504
|
+
]);
|
|
505
|
+
forms.delete("");
|
|
506
|
+
return [...forms].sort((a, b) => b.length - a.length);
|
|
507
|
+
}
|
|
508
|
+
function redactVaultSecrets(userId, text) {
|
|
509
|
+
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));
|
|
510
|
+
if (candidates.length === 0)
|
|
511
|
+
return text;
|
|
512
|
+
const candidatesByFirstCharacter = new Map;
|
|
513
|
+
for (const candidate of candidates) {
|
|
514
|
+
const first = candidate.form[0];
|
|
515
|
+
if (!first)
|
|
516
|
+
continue;
|
|
517
|
+
const bucket = candidatesByFirstCharacter.get(first) ?? [];
|
|
518
|
+
bucket.push(candidate);
|
|
519
|
+
candidatesByFirstCharacter.set(first, bucket);
|
|
520
|
+
}
|
|
521
|
+
let redacted = "";
|
|
522
|
+
let offset = 0;
|
|
523
|
+
while (offset < text.length) {
|
|
524
|
+
const match = candidatesByFirstCharacter.get(text[offset] ?? "")?.find((candidate) => text.startsWith(candidate.form, offset));
|
|
525
|
+
if (!match) {
|
|
526
|
+
redacted += text[offset];
|
|
527
|
+
offset += 1;
|
|
528
|
+
continue;
|
|
529
|
+
}
|
|
530
|
+
redacted += `[REDACTED:${match.key}]`;
|
|
531
|
+
offset += match.form.length;
|
|
532
|
+
}
|
|
533
|
+
return redacted;
|
|
534
|
+
}
|
|
535
|
+
|
|
256
536
|
// ../../packages/core/src/agents/auth-check.ts
|
|
257
537
|
var defaultAgentAuthHost = {
|
|
258
538
|
codexAuthFilePath,
|
|
@@ -262,14 +542,20 @@ var defaultAgentAuthHost = {
|
|
|
262
542
|
operatingSystem: platform,
|
|
263
543
|
hasMacOsCredential(service) {
|
|
264
544
|
try {
|
|
265
|
-
|
|
545
|
+
execFileSync2("security", ["find-generic-password", "-s", service], { stdio: "ignore" });
|
|
266
546
|
return true;
|
|
267
547
|
} catch {
|
|
268
548
|
return false;
|
|
269
549
|
}
|
|
270
|
-
}
|
|
550
|
+
},
|
|
551
|
+
getVaultValue: vaultGetValue
|
|
271
552
|
};
|
|
272
|
-
function
|
|
553
|
+
function hasMaestroCredential(host, key, userId) {
|
|
554
|
+
if (userId && host.getVaultValue(userId, key)?.trim())
|
|
555
|
+
return true;
|
|
556
|
+
return Boolean(host.environment[key]?.trim());
|
|
557
|
+
}
|
|
558
|
+
function checkAgentAuth(agent, host = defaultAgentAuthHost, userId) {
|
|
273
559
|
switch (agent) {
|
|
274
560
|
case "codex": {
|
|
275
561
|
const path = host.codexAuthFilePath();
|
|
@@ -292,7 +578,7 @@ function checkAgentAuth(agent, host = defaultAgentAuthHost) {
|
|
|
292
578
|
error: "claude is not logged in (no macOS keychain entry 'Claude Code-credentials'). Run `claude` and complete login first"
|
|
293
579
|
};
|
|
294
580
|
}
|
|
295
|
-
const path =
|
|
581
|
+
const path = join3(host.homeDirectory(), ".claude", ".credentials.json");
|
|
296
582
|
if (host.exists(path))
|
|
297
583
|
return { ok: true };
|
|
298
584
|
return {
|
|
@@ -301,35 +587,35 @@ function checkAgentAuth(agent, host = defaultAgentAuthHost) {
|
|
|
301
587
|
};
|
|
302
588
|
}
|
|
303
589
|
case "maestro": {
|
|
304
|
-
if (host
|
|
590
|
+
if (hasMaestroCredential(host, "DEEPSEEK_API_KEY", userId) || hasMaestroCredential(host, "MOONSHOT_API_KEY", userId)) {
|
|
305
591
|
return { ok: true };
|
|
306
592
|
}
|
|
307
593
|
return {
|
|
308
594
|
ok: false,
|
|
309
|
-
error: "maestro is not authenticated (
|
|
595
|
+
error: "maestro is not authenticated (set DEEPSEEK_API_KEY or MOONSHOT_API_KEY via /vault set, or as an env var)"
|
|
310
596
|
};
|
|
311
597
|
}
|
|
312
598
|
}
|
|
313
599
|
}
|
|
314
|
-
function checkAgentModelAuth(agent, model, host = defaultAgentAuthHost) {
|
|
600
|
+
function checkAgentModelAuth(agent, model, host = defaultAgentAuthHost, userId) {
|
|
315
601
|
if (agent !== "maestro")
|
|
316
|
-
return checkAgentAuth(agent, host);
|
|
602
|
+
return checkAgentAuth(agent, host, userId);
|
|
317
603
|
if (model.startsWith("kimi")) {
|
|
318
|
-
return host
|
|
604
|
+
return hasMaestroCredential(host, "MOONSHOT_API_KEY", userId) ? { ok: true } : {
|
|
319
605
|
ok: false,
|
|
320
|
-
error: `maestro is not authenticated for model '${model}' (MOONSHOT_API_KEY env var
|
|
606
|
+
error: `maestro is not authenticated for model '${model}' (set MOONSHOT_API_KEY via /vault set, or as an env var)`
|
|
321
607
|
};
|
|
322
608
|
}
|
|
323
609
|
if (model.startsWith("deepseek")) {
|
|
324
|
-
return host
|
|
610
|
+
return hasMaestroCredential(host, "DEEPSEEK_API_KEY", userId) ? { ok: true } : {
|
|
325
611
|
ok: false,
|
|
326
|
-
error: `maestro is not authenticated for model '${model}' (DEEPSEEK_API_KEY env var
|
|
612
|
+
error: `maestro is not authenticated for model '${model}' (set DEEPSEEK_API_KEY via /vault set, or as an env var)`
|
|
327
613
|
};
|
|
328
614
|
}
|
|
329
|
-
return checkAgentAuth(agent, host);
|
|
615
|
+
return checkAgentAuth(agent, host, userId);
|
|
330
616
|
}
|
|
331
617
|
// ../../packages/core/src/agents/codex-tree-kill.ts
|
|
332
|
-
import { execFileSync as
|
|
618
|
+
import { execFileSync as execFileSync3 } from "child_process";
|
|
333
619
|
|
|
334
620
|
// ../../packages/core/src/agents/codex-tree-manager.ts
|
|
335
621
|
function createCodexTreeManager(host, options = {}) {
|
|
@@ -515,7 +801,7 @@ function createCodexTreeManager(host, options = {}) {
|
|
|
515
801
|
// ../../packages/core/src/agents/codex-tree-kill.ts
|
|
516
802
|
function execText(command, args) {
|
|
517
803
|
try {
|
|
518
|
-
return
|
|
804
|
+
return execFileSync3(command, args, {
|
|
519
805
|
encoding: "utf8",
|
|
520
806
|
stdio: ["ignore", "pipe", "ignore"]
|
|
521
807
|
}).trim();
|
|
@@ -550,23 +836,23 @@ var killOwnedCodexTreesForShutdown = manager.killOwnedTreesForShutdown;
|
|
|
550
836
|
import { existsSync as existsSync7, unlinkSync as unlinkSync7 } from "fs";
|
|
551
837
|
|
|
552
838
|
// ../../packages/core/src/agents/claude-registry.ts
|
|
553
|
-
import { existsSync as existsSync4, mkdirSync as
|
|
839
|
+
import { existsSync as existsSync4, mkdirSync as mkdirSync5, readdirSync, renameSync as renameSync2, unlinkSync as unlinkSync3 } from "fs";
|
|
554
840
|
import { homedir as homedir4 } from "os";
|
|
555
|
-
import { join as
|
|
841
|
+
import { join as join6 } from "path";
|
|
556
842
|
|
|
557
843
|
// ../../packages/core/src/agents/rollout/claude.ts
|
|
558
844
|
import { randomUUID } from "crypto";
|
|
559
845
|
import { existsSync as existsSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "fs";
|
|
560
846
|
import { homedir as homedir3 } from "os";
|
|
561
|
-
import { join as
|
|
847
|
+
import { join as join5 } from "path";
|
|
562
848
|
|
|
563
849
|
// ../../packages/core/src/agents/rollout/shared.ts
|
|
564
|
-
import { mkdirSync as
|
|
565
|
-
import { dirname as dirname2, join as
|
|
850
|
+
import { mkdirSync as mkdirSync3 } from "fs";
|
|
851
|
+
import { dirname as dirname2, join as join4, resolve as resolve2 } from "path";
|
|
566
852
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
567
853
|
var __dirname2 = dirname2(fileURLToPath2(import.meta.url));
|
|
568
854
|
var trustedWorkspaceRoots = [WORKSPACE_DIR, DM_WORKSPACE_DIR, TOPIC_WORKSPACE_DIR].map((root) => resolve2(root));
|
|
569
|
-
var FIXTURES_DIR =
|
|
855
|
+
var FIXTURES_DIR = join4(__dirname2, "..", "fixtures");
|
|
570
856
|
function clone(obj) {
|
|
571
857
|
return structuredClone(obj);
|
|
572
858
|
}
|
|
@@ -586,7 +872,7 @@ function assertCwdInWorkspace(cwd) {
|
|
|
586
872
|
function ensureCwdExists(cwd) {
|
|
587
873
|
assertCwdInWorkspace(cwd);
|
|
588
874
|
try {
|
|
589
|
-
|
|
875
|
+
mkdirSync3(cwd, { recursive: true });
|
|
590
876
|
} catch (err) {
|
|
591
877
|
logger.warn({ err, cwd }, "rollout: ensureCwdExists failed \u2014 caller should pre-create cwd");
|
|
592
878
|
}
|
|
@@ -676,7 +962,7 @@ import {
|
|
|
676
962
|
appendFileSync,
|
|
677
963
|
closeSync,
|
|
678
964
|
fsyncSync,
|
|
679
|
-
mkdirSync as
|
|
965
|
+
mkdirSync as mkdirSync4,
|
|
680
966
|
openSync,
|
|
681
967
|
readFileSync as readFileSync2,
|
|
682
968
|
renameSync,
|
|
@@ -739,7 +1025,7 @@ function isStaleLock(lockPath) {
|
|
|
739
1025
|
}
|
|
740
1026
|
}
|
|
741
1027
|
function appendJsonlLine(filePath, line) {
|
|
742
|
-
|
|
1028
|
+
mkdirSync4(dirname3(filePath), { recursive: true });
|
|
743
1029
|
const lockPath = `${filePath}${LOCK_SUFFIX}`;
|
|
744
1030
|
const payload = line.endsWith(`
|
|
745
1031
|
`) ? line : `${line}
|
|
@@ -772,7 +1058,7 @@ function appendJsonlLine(filePath, line) {
|
|
|
772
1058
|
}
|
|
773
1059
|
function writeJsonlFile(filePath, entries) {
|
|
774
1060
|
const dir = dirname3(filePath);
|
|
775
|
-
|
|
1061
|
+
mkdirSync4(dir, { recursive: true });
|
|
776
1062
|
const tmpPath = `${filePath}.tmp-${process.pid}-${Date.now()}-${Math.random().toString(16).slice(2)}`;
|
|
777
1063
|
const payload = `${entries.map((e) => JSON.stringify(e)).join(`
|
|
778
1064
|
`)}
|
|
@@ -817,7 +1103,7 @@ var _attachmentsCache = null;
|
|
|
817
1103
|
function loadClaudeAttachments() {
|
|
818
1104
|
if (_attachmentsCache)
|
|
819
1105
|
return _attachmentsCache;
|
|
820
|
-
const raw = readFileSync3(
|
|
1106
|
+
const raw = readFileSync3(join5(FIXTURES_DIR, "claude-attachments.jsonl"), "utf8");
|
|
821
1107
|
const lines = parseJsonlText(raw);
|
|
822
1108
|
if (lines.length < 2) {
|
|
823
1109
|
throw new Error(`loadClaudeAttachments: expected >=2 entries in claude-attachments.jsonl, got ${lines.length}`);
|
|
@@ -921,8 +1207,8 @@ function writeClaudeRollout(opts) {
|
|
|
921
1207
|
});
|
|
922
1208
|
lastUuid = assistantUuid;
|
|
923
1209
|
}
|
|
924
|
-
const projectsDir =
|
|
925
|
-
const path =
|
|
1210
|
+
const projectsDir = join5(homedir3(), ".claude", "projects", encodeClaudeCwd(opts.cwd));
|
|
1211
|
+
const path = join5(projectsDir, `${sessionId}.jsonl`);
|
|
926
1212
|
writeJsonlFile(path, lines);
|
|
927
1213
|
logger.info({ sessionId, path, pairs: pairs.length }, "writeClaudeRollout: synthetic rollout placed");
|
|
928
1214
|
return { sessionId, rolloutPath: path };
|
|
@@ -967,15 +1253,15 @@ var claudeRegistry = {
|
|
|
967
1253
|
const result = await forkSession(parentSessionId, {
|
|
968
1254
|
...title ? { title } : {}
|
|
969
1255
|
});
|
|
970
|
-
const projectsRoot =
|
|
971
|
-
const destDir =
|
|
972
|
-
const destPath =
|
|
1256
|
+
const projectsRoot = join6(homedir4(), ".claude", "projects");
|
|
1257
|
+
const destDir = join6(projectsRoot, encodeClaudeCwd(cwd));
|
|
1258
|
+
const destPath = join6(destDir, `${result.sessionId}.jsonl`);
|
|
973
1259
|
if (!existsSync4(destPath)) {
|
|
974
|
-
const sourcePath = readdirSync(projectsRoot).map((d) =>
|
|
1260
|
+
const sourcePath = readdirSync(projectsRoot).map((d) => join6(projectsRoot, d, `${result.sessionId}.jsonl`)).find((p) => existsSync4(p));
|
|
975
1261
|
if (!sourcePath) {
|
|
976
1262
|
throw new Error(`claude forkSession: fork rollout ${result.sessionId}.jsonl not found under ${projectsRoot}`);
|
|
977
1263
|
}
|
|
978
|
-
|
|
1264
|
+
mkdirSync5(destDir, { recursive: true });
|
|
979
1265
|
renameSync2(sourcePath, destPath);
|
|
980
1266
|
}
|
|
981
1267
|
return {
|
|
@@ -984,10 +1270,10 @@ var claudeRegistry = {
|
|
|
984
1270
|
};
|
|
985
1271
|
},
|
|
986
1272
|
async cleanupRollouts({ cwd, sessionIds }) {
|
|
987
|
-
const projectsDir =
|
|
1273
|
+
const projectsDir = join6(homedir4(), ".claude", "projects", encodeClaudeCwd(cwd));
|
|
988
1274
|
const failures = [];
|
|
989
1275
|
for (const sid of sessionIds) {
|
|
990
|
-
const path =
|
|
1276
|
+
const path = join6(projectsDir, `${sid}.jsonl`);
|
|
991
1277
|
try {
|
|
992
1278
|
unlinkSync3(path);
|
|
993
1279
|
} catch (e) {
|
|
@@ -1006,21 +1292,21 @@ var claudeRegistry = {
|
|
|
1006
1292
|
// ../../packages/core/src/agents/codex-registry.ts
|
|
1007
1293
|
import { unlinkSync as unlinkSync6 } from "fs";
|
|
1008
1294
|
import { homedir as homedir7 } from "os";
|
|
1009
|
-
import { join as
|
|
1295
|
+
import { join as join10 } from "path";
|
|
1010
1296
|
|
|
1011
1297
|
// ../../packages/core/src/agents/rollout/codex.ts
|
|
1012
|
-
import { randomBytes as
|
|
1298
|
+
import { randomBytes as randomBytes3 } from "crypto";
|
|
1013
1299
|
import { existsSync as existsSync5, readFileSync as readFileSync4, statSync as statSync2, unlinkSync as unlinkSync4 } from "fs";
|
|
1014
1300
|
import { homedir as homedir5 } from "os";
|
|
1015
|
-
import { join as
|
|
1301
|
+
import { join as join7 } from "path";
|
|
1016
1302
|
function codexSessionsDir() {
|
|
1017
|
-
return
|
|
1303
|
+
return join7(process.env.CODEX_HOME || join7(homedir5(), ".codex"), "sessions");
|
|
1018
1304
|
}
|
|
1019
1305
|
var _shellCache = null;
|
|
1020
1306
|
function loadCodexShell() {
|
|
1021
1307
|
if (_shellCache)
|
|
1022
1308
|
return _shellCache;
|
|
1023
|
-
const raw = readFileSync4(
|
|
1309
|
+
const raw = readFileSync4(join7(FIXTURES_DIR, "codex-shell.jsonl"), "utf8");
|
|
1024
1310
|
const lines = parseJsonlText(raw);
|
|
1025
1311
|
if (lines.length < 5) {
|
|
1026
1312
|
throw new Error(`loadCodexShell: expected >=5 entries in codex-shell.jsonl, got ${lines.length}`);
|
|
@@ -1037,7 +1323,7 @@ function loadCodexShell() {
|
|
|
1037
1323
|
function uuidv7() {
|
|
1038
1324
|
const ts = Date.now();
|
|
1039
1325
|
const tsHex = ts.toString(16).padStart(12, "0");
|
|
1040
|
-
const rand =
|
|
1326
|
+
const rand = randomBytes3(10);
|
|
1041
1327
|
rand[0] = rand[0] & 15 | 112;
|
|
1042
1328
|
rand[2] = rand[2] & 63 | 128;
|
|
1043
1329
|
return [
|
|
@@ -1102,18 +1388,18 @@ function readLatestCodexContextUsage(threadId) {
|
|
|
1102
1388
|
try {
|
|
1103
1389
|
if (buckets) {
|
|
1104
1390
|
for (const bucket of buckets) {
|
|
1105
|
-
const dir =
|
|
1391
|
+
const dir = join7(sessionsDir, bucket);
|
|
1106
1392
|
if (!existsSync5(dir))
|
|
1107
1393
|
continue;
|
|
1108
1394
|
const glob = new Bun.Glob(`rollout-*-${threadId}.jsonl`);
|
|
1109
1395
|
for (const rel of glob.scanSync({ cwd: dir, onlyFiles: true })) {
|
|
1110
|
-
candidates.push(
|
|
1396
|
+
candidates.push(join7(dir, rel));
|
|
1111
1397
|
}
|
|
1112
1398
|
}
|
|
1113
1399
|
} else {
|
|
1114
1400
|
const glob = new Bun.Glob(`**/rollout-*-${threadId}.jsonl`);
|
|
1115
1401
|
for (const rel of glob.scanSync({ cwd: sessionsDir, onlyFiles: true })) {
|
|
1116
|
-
candidates.push(
|
|
1402
|
+
candidates.push(join7(sessionsDir, rel));
|
|
1117
1403
|
}
|
|
1118
1404
|
}
|
|
1119
1405
|
const path = candidates.sort((a, b) => statSync2(b).mtimeMs - statSync2(a).mtimeMs)[0];
|
|
@@ -1157,19 +1443,19 @@ function latestCodexRolloutPath(threadId) {
|
|
|
1157
1443
|
try {
|
|
1158
1444
|
if (buckets) {
|
|
1159
1445
|
for (const bucket of buckets) {
|
|
1160
|
-
const dir =
|
|
1446
|
+
const dir = join7(sessionsDir, bucket);
|
|
1161
1447
|
if (!existsSync5(dir))
|
|
1162
1448
|
continue;
|
|
1163
1449
|
const glob = new Bun.Glob(`rollout-*-${threadId}.jsonl`);
|
|
1164
1450
|
for (const rel of glob.scanSync({ cwd: dir, onlyFiles: true })) {
|
|
1165
|
-
candidates.push(
|
|
1451
|
+
candidates.push(join7(dir, rel));
|
|
1166
1452
|
}
|
|
1167
1453
|
}
|
|
1168
1454
|
}
|
|
1169
1455
|
if (candidates.length === 0) {
|
|
1170
1456
|
const glob = new Bun.Glob(`**/rollout-*-${threadId}.jsonl`);
|
|
1171
1457
|
for (const rel of glob.scanSync({ cwd: sessionsDir, onlyFiles: true })) {
|
|
1172
|
-
candidates.push(
|
|
1458
|
+
candidates.push(join7(sessionsDir, rel));
|
|
1173
1459
|
}
|
|
1174
1460
|
}
|
|
1175
1461
|
return candidates.sort((a, b) => statSync2(b).mtimeMs - statSync2(a).mtimeMs)[0];
|
|
@@ -1278,8 +1564,8 @@ function writeCodexRollout(opts) {
|
|
|
1278
1564
|
const mm = String(now.getUTCMonth() + 1).padStart(2, "0");
|
|
1279
1565
|
const dd = String(now.getUTCDate()).padStart(2, "0");
|
|
1280
1566
|
const tsStr = tsIso.replace(/[:.]/g, "-").slice(0, 19);
|
|
1281
|
-
const dir =
|
|
1282
|
-
const path =
|
|
1567
|
+
const dir = join7(codexSessionsDir(), String(yyyy), mm, dd);
|
|
1568
|
+
const path = join7(dir, `rollout-${tsStr}-${threadId}.jsonl`);
|
|
1283
1569
|
if (opts.threadId) {
|
|
1284
1570
|
sweepPriorRolloutsForThread(opts.threadId);
|
|
1285
1571
|
}
|
|
@@ -1295,13 +1581,13 @@ function sweepPriorRolloutsForThread(threadId) {
|
|
|
1295
1581
|
return;
|
|
1296
1582
|
}
|
|
1297
1583
|
for (const bucket of buckets) {
|
|
1298
|
-
const dir =
|
|
1584
|
+
const dir = join7(sessionsDir, bucket);
|
|
1299
1585
|
if (!existsSync5(dir))
|
|
1300
1586
|
continue;
|
|
1301
1587
|
try {
|
|
1302
1588
|
const glob = new Bun.Glob(`rollout-*-${threadId}.jsonl`);
|
|
1303
1589
|
for (const rel of glob.scanSync({ cwd: dir, onlyFiles: true })) {
|
|
1304
|
-
const fullPath =
|
|
1590
|
+
const fullPath = join7(dir, rel);
|
|
1305
1591
|
try {
|
|
1306
1592
|
unlinkSync4(fullPath);
|
|
1307
1593
|
} catch (e) {
|
|
@@ -1319,7 +1605,7 @@ function sweepPriorRolloutsFullTree(threadId, sessionsDir) {
|
|
|
1319
1605
|
try {
|
|
1320
1606
|
const glob = new Bun.Glob(`**/rollout-*-${threadId}.jsonl`);
|
|
1321
1607
|
for (const rel of glob.scanSync({ cwd: sessionsDir, onlyFiles: true })) {
|
|
1322
|
-
const fullPath =
|
|
1608
|
+
const fullPath = join7(sessionsDir, rel);
|
|
1323
1609
|
try {
|
|
1324
1610
|
unlinkSync4(fullPath);
|
|
1325
1611
|
} catch (e) {
|
|
@@ -1365,97 +1651,44 @@ function decodeUuidV7Timestamp(threadId) {
|
|
|
1365
1651
|
return null;
|
|
1366
1652
|
const MIN = 1577836800000;
|
|
1367
1653
|
const MAX = 4102444800000;
|
|
1368
|
-
if (ms < MIN || ms > MAX)
|
|
1369
|
-
return null;
|
|
1370
|
-
return ms;
|
|
1371
|
-
}
|
|
1372
|
-
function formatDateBucket(d) {
|
|
1373
|
-
const yyyy = d.getUTCFullYear();
|
|
1374
|
-
const mm = String(d.getUTCMonth() + 1).padStart(2, "0");
|
|
1375
|
-
const dd = String(d.getUTCDate()).padStart(2, "0");
|
|
1376
|
-
return `${yyyy}/${mm}/${dd}`;
|
|
1377
|
-
}
|
|
1378
|
-
|
|
1379
|
-
// ../../packages/core/src/storage/conversations.ts
|
|
1380
|
-
import {
|
|
1381
|
-
existsSync as existsSync6,
|
|
1382
|
-
mkdirSync as
|
|
1383
|
-
readFileSync as readFileSync5,
|
|
1384
|
-
renameSync as renameSync3,
|
|
1385
|
-
unlinkSync as unlinkSync5,
|
|
1386
|
-
writeFileSync as writeFileSync4
|
|
1387
|
-
} from "fs";
|
|
1388
|
-
import { dirname as dirname5, join as
|
|
1389
|
-
|
|
1390
|
-
// ../../packages/core/src/security/sanitize.ts
|
|
1391
|
-
function sanitizeTopicName(name, lowercase = false) {
|
|
1392
|
-
const safe = name.replace(/[^a-zA-Z0-9\uAC00-\uD7A3_-]/g, "_") || "_";
|
|
1393
|
-
return lowercase ? safe.toLowerCase() : safe;
|
|
1394
|
-
}
|
|
1395
|
-
function sanitizeFileName(name) {
|
|
1396
|
-
const safe = name.replace(/[^a-zA-Z0-9._-]/g, "_") || "_";
|
|
1397
|
-
if (safe === "." || safe === "..")
|
|
1398
|
-
return "_";
|
|
1399
|
-
return safe;
|
|
1400
|
-
}
|
|
1401
|
-
|
|
1402
|
-
// ../../packages/core/src/storage/storage-host.ts
|
|
1403
|
-
import { mkdirSync as mkdirSync5 } from "fs";
|
|
1404
|
-
import { homedir as homedir6 } from "os";
|
|
1405
|
-
import { dirname as dirname4, join as join7, resolve as resolve3 } from "path";
|
|
1406
|
-
|
|
1407
|
-
// ../../packages/core/src/storage/sqlite.ts
|
|
1408
|
-
var isBun = typeof process.versions.bun === "string";
|
|
1409
|
-
var Database;
|
|
1410
|
-
if (isBun) {
|
|
1411
|
-
({ Database } = await import("bun:sqlite"));
|
|
1412
|
-
} else {
|
|
1413
|
-
const nodeSqliteSpecifier = ["node", "sqlite"].join(":");
|
|
1414
|
-
const { DatabaseSync } = await import(nodeSqliteSpecifier);
|
|
1415
|
-
|
|
1416
|
-
class NodeDatabase {
|
|
1417
|
-
#db;
|
|
1418
|
-
constructor(path, options = {}) {
|
|
1419
|
-
this.#db = options.readonly ? new DatabaseSync(path, { readOnly: true }) : new DatabaseSync(path);
|
|
1420
|
-
}
|
|
1421
|
-
query(sql) {
|
|
1422
|
-
return this.#db.prepare(sql);
|
|
1423
|
-
}
|
|
1424
|
-
prepare(sql) {
|
|
1425
|
-
return this.#db.prepare(sql);
|
|
1426
|
-
}
|
|
1427
|
-
exec(sql) {
|
|
1428
|
-
this.#db.exec(sql);
|
|
1429
|
-
}
|
|
1430
|
-
run(sql, ...params) {
|
|
1431
|
-
return this.#db.prepare(sql).run(...params);
|
|
1432
|
-
}
|
|
1433
|
-
transaction(fn) {
|
|
1434
|
-
const run = (begin) => (...args) => {
|
|
1435
|
-
this.#db.exec(begin);
|
|
1436
|
-
try {
|
|
1437
|
-
const result = fn(...args);
|
|
1438
|
-
this.#db.exec("COMMIT");
|
|
1439
|
-
return result;
|
|
1440
|
-
} catch (err) {
|
|
1441
|
-
this.#db.exec("ROLLBACK");
|
|
1442
|
-
throw err;
|
|
1443
|
-
}
|
|
1444
|
-
};
|
|
1445
|
-
const tx = run("BEGIN");
|
|
1446
|
-
tx.deferred = run("BEGIN DEFERRED");
|
|
1447
|
-
tx.immediate = run("BEGIN IMMEDIATE");
|
|
1448
|
-
tx.exclusive = run("BEGIN EXCLUSIVE");
|
|
1449
|
-
return tx;
|
|
1450
|
-
}
|
|
1451
|
-
close() {
|
|
1452
|
-
this.#db.close();
|
|
1453
|
-
}
|
|
1454
|
-
}
|
|
1455
|
-
Database = NodeDatabase;
|
|
1654
|
+
if (ms < MIN || ms > MAX)
|
|
1655
|
+
return null;
|
|
1656
|
+
return ms;
|
|
1657
|
+
}
|
|
1658
|
+
function formatDateBucket(d) {
|
|
1659
|
+
const yyyy = d.getUTCFullYear();
|
|
1660
|
+
const mm = String(d.getUTCMonth() + 1).padStart(2, "0");
|
|
1661
|
+
const dd = String(d.getUTCDate()).padStart(2, "0");
|
|
1662
|
+
return `${yyyy}/${mm}/${dd}`;
|
|
1663
|
+
}
|
|
1664
|
+
|
|
1665
|
+
// ../../packages/core/src/storage/conversations.ts
|
|
1666
|
+
import {
|
|
1667
|
+
existsSync as existsSync6,
|
|
1668
|
+
mkdirSync as mkdirSync7,
|
|
1669
|
+
readFileSync as readFileSync5,
|
|
1670
|
+
renameSync as renameSync3,
|
|
1671
|
+
unlinkSync as unlinkSync5,
|
|
1672
|
+
writeFileSync as writeFileSync4
|
|
1673
|
+
} from "fs";
|
|
1674
|
+
import { dirname as dirname5, join as join9 } from "path";
|
|
1675
|
+
|
|
1676
|
+
// ../../packages/core/src/security/sanitize.ts
|
|
1677
|
+
function sanitizeTopicName(name, lowercase = false) {
|
|
1678
|
+
const safe = name.replace(/[^a-zA-Z0-9\uAC00-\uD7A3_-]/g, "_") || "_";
|
|
1679
|
+
return lowercase ? safe.toLowerCase() : safe;
|
|
1680
|
+
}
|
|
1681
|
+
function sanitizeFileName(name) {
|
|
1682
|
+
const safe = name.replace(/[^a-zA-Z0-9._-]/g, "_") || "_";
|
|
1683
|
+
if (safe === "." || safe === "..")
|
|
1684
|
+
return "_";
|
|
1685
|
+
return safe;
|
|
1456
1686
|
}
|
|
1457
1687
|
|
|
1458
1688
|
// ../../packages/core/src/storage/storage-host.ts
|
|
1689
|
+
import { mkdirSync as mkdirSync6 } from "fs";
|
|
1690
|
+
import { homedir as homedir6 } from "os";
|
|
1691
|
+
import { dirname as dirname4, join as join8, resolve as resolve3 } from "path";
|
|
1459
1692
|
var configuredHost = {};
|
|
1460
1693
|
var fallbackDatabase = null;
|
|
1461
1694
|
var fallbackDatabasePath = null;
|
|
@@ -1467,16 +1700,16 @@ function envPath(name, fallback) {
|
|
|
1467
1700
|
return resolve3(value || fallback);
|
|
1468
1701
|
}
|
|
1469
1702
|
function defaultStateDir() {
|
|
1470
|
-
return envPath("NEGOTIUM_STATE_DIR",
|
|
1703
|
+
return envPath("NEGOTIUM_STATE_DIR", join8(homedir6(), ".negotium"));
|
|
1471
1704
|
}
|
|
1472
1705
|
function defaultDataDir() {
|
|
1473
|
-
return envPath("NEGOTIUM_DATA_DIR",
|
|
1706
|
+
return envPath("NEGOTIUM_DATA_DIR", join8(defaultStateDir(), "data"));
|
|
1474
1707
|
}
|
|
1475
1708
|
function defaultWorkspaceDir() {
|
|
1476
|
-
return envPath("NEGOTIUM_WORKSPACE_DIR",
|
|
1709
|
+
return envPath("NEGOTIUM_WORKSPACE_DIR", join8(defaultStateDir(), "workspace"));
|
|
1477
1710
|
}
|
|
1478
1711
|
function defaultSessionsDatabasePath() {
|
|
1479
|
-
return envPath("SESSIONS_DB_PATH",
|
|
1712
|
+
return envPath("SESSIONS_DB_PATH", join8(resolveStorageDataDir(), "sessions.db"));
|
|
1480
1713
|
}
|
|
1481
1714
|
function initializeDatabase(database) {
|
|
1482
1715
|
database.exec("PRAGMA journal_mode = WAL");
|
|
@@ -1493,7 +1726,7 @@ function defaultDatabase() {
|
|
|
1493
1726
|
return fallbackDatabase;
|
|
1494
1727
|
if (fallbackDatabase)
|
|
1495
1728
|
fallbackDatabase.close();
|
|
1496
|
-
|
|
1729
|
+
mkdirSync6(dirname4(path), { recursive: true });
|
|
1497
1730
|
fallbackDatabase = new Database(path, { create: true });
|
|
1498
1731
|
fallbackDatabasePath = path;
|
|
1499
1732
|
initializeDatabase(fallbackDatabase);
|
|
@@ -1509,7 +1742,7 @@ function resolveStorageWorkspaceDir() {
|
|
|
1509
1742
|
return configuredHost.workspaceDir ?? defaultWorkspaceDir();
|
|
1510
1743
|
}
|
|
1511
1744
|
function resolveStorageSharedWikiDir() {
|
|
1512
|
-
return configuredHost.sharedWikiDir ??
|
|
1745
|
+
return configuredHost.sharedWikiDir ?? join8(resolveStorageWorkspaceDir(), "wiki");
|
|
1513
1746
|
}
|
|
1514
1747
|
function registerStorageSchemaInitializer(initialize, priority = 100) {
|
|
1515
1748
|
schemaInitializers.push({ initialize, priority });
|
|
@@ -1563,14 +1796,14 @@ function safeUserIdComponent(userId) {
|
|
|
1563
1796
|
return str;
|
|
1564
1797
|
}
|
|
1565
1798
|
function conversationDir(userId) {
|
|
1566
|
-
return
|
|
1799
|
+
return join9(resolveStorageDataDir(), "conversations", safeUserIdComponent(userId));
|
|
1567
1800
|
}
|
|
1568
1801
|
function topicFilename(topicName) {
|
|
1569
1802
|
const t = sanitizeTopicName(topicName, true);
|
|
1570
1803
|
return `${t}.jsonl`;
|
|
1571
1804
|
}
|
|
1572
1805
|
function getConversationPath(userId, topicName) {
|
|
1573
|
-
return
|
|
1806
|
+
return join9(conversationDir(userId), topicFilename(topicName));
|
|
1574
1807
|
}
|
|
1575
1808
|
function appendConversationEvent(userId, topicName, agent, event) {
|
|
1576
1809
|
try {
|
|
@@ -1586,7 +1819,7 @@ function appendConversationEventStrict(userId, topicName, agent, event) {
|
|
|
1586
1819
|
agent,
|
|
1587
1820
|
event
|
|
1588
1821
|
};
|
|
1589
|
-
|
|
1822
|
+
mkdirSync7(dirname5(path), { recursive: true });
|
|
1590
1823
|
appendJsonlLine(path, JSON.stringify(entry));
|
|
1591
1824
|
}
|
|
1592
1825
|
function readConversation(userId, topicName) {
|
|
@@ -1655,13 +1888,13 @@ var codexRegistry = {
|
|
|
1655
1888
|
async cleanupRollouts({ sessionIds }) {
|
|
1656
1889
|
if (sessionIds.length === 0)
|
|
1657
1890
|
return;
|
|
1658
|
-
const sessionsDir =
|
|
1891
|
+
const sessionsDir = join10(process.env.CODEX_HOME || join10(homedir7(), ".codex"), "sessions");
|
|
1659
1892
|
const failures = [];
|
|
1660
1893
|
for (const tid of sessionIds) {
|
|
1661
1894
|
try {
|
|
1662
1895
|
const glob = new Bun.Glob(`**/rollout-*-${tid}.jsonl`);
|
|
1663
1896
|
for await (const rel of glob.scan({ cwd: sessionsDir, onlyFiles: true })) {
|
|
1664
|
-
const path =
|
|
1897
|
+
const path = join10(sessionsDir, rel);
|
|
1665
1898
|
try {
|
|
1666
1899
|
unlinkSync6(path);
|
|
1667
1900
|
} catch (e) {
|
|
@@ -1747,7 +1980,7 @@ function cleanupAgentFork(handle) {
|
|
|
1747
1980
|
defaultForkHelpers.cleanupAgentFork(handle);
|
|
1748
1981
|
}
|
|
1749
1982
|
// ../../packages/core/src/agents/archiver.ts
|
|
1750
|
-
import { randomUUID as
|
|
1983
|
+
import { randomUUID as randomUUID5 } from "crypto";
|
|
1751
1984
|
import { existsSync as existsSync13, readdirSync as readdirSync2, readFileSync as readFileSync10, statSync as statSync4 } from "fs";
|
|
1752
1985
|
import { join as join15 } from "path";
|
|
1753
1986
|
|
|
@@ -1932,8 +2165,8 @@ function peerSessionBridgeIpcEnv() {
|
|
|
1932
2165
|
}
|
|
1933
2166
|
|
|
1934
2167
|
// ../../packages/core/src/platform/background-bash/manager.ts
|
|
1935
|
-
import { execFileSync as
|
|
1936
|
-
import { randomBytes as
|
|
2168
|
+
import { execFileSync as execFileSync4, spawn } from "child_process";
|
|
2169
|
+
import { randomBytes as randomBytes4 } from "crypto";
|
|
1937
2170
|
|
|
1938
2171
|
// ../../packages/core/src/platform/delay.ts
|
|
1939
2172
|
var delay = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
@@ -1950,7 +2183,7 @@ function makeBgBashKey(_userId, _topic) {
|
|
|
1950
2183
|
}
|
|
1951
2184
|
function defaultPortPids(port) {
|
|
1952
2185
|
try {
|
|
1953
|
-
return
|
|
2186
|
+
return execFileSync4("lsof", ["-i", `:${port}`, "-t"], { stdio: "pipe" }).toString().trim().split(`
|
|
1954
2187
|
`).map((pid) => Number.parseInt(pid, 10)).filter((pid) => !Number.isNaN(pid));
|
|
1955
2188
|
} catch {
|
|
1956
2189
|
return [];
|
|
@@ -1961,8 +2194,8 @@ function createBackgroundBashManager(options = {}) {
|
|
|
1961
2194
|
const usedPorts = new Set;
|
|
1962
2195
|
const spawning = new Map;
|
|
1963
2196
|
const knownContexts = new Map;
|
|
1964
|
-
const runtimeCapability = options.capability ??
|
|
1965
|
-
const runtimeServerId = options.serverId ??
|
|
2197
|
+
const runtimeCapability = options.capability ?? randomBytes4(32).toString("hex");
|
|
2198
|
+
const runtimeServerId = options.serverId ?? randomBytes4(16).toString("hex");
|
|
1966
2199
|
const serverFile = options.serverFile ?? BACKGROUND_BASH_SERVER;
|
|
1967
2200
|
const basePort = options.basePort ?? BG_BASH_BASE_PORT;
|
|
1968
2201
|
const maxPort = options.maxPort ?? BG_BASH_MAX_PORT;
|
|
@@ -2201,23 +2434,23 @@ function browserOwnerForContext(ctx) {
|
|
|
2201
2434
|
function playwrightTransport(port, owner, capability, agent) {
|
|
2202
2435
|
const ownerCapability = browserOwnerCapability(capability, owner);
|
|
2203
2436
|
if (agent === "codex") {
|
|
2437
|
+
const query2 = new URLSearchParams({ owner });
|
|
2204
2438
|
return {
|
|
2205
|
-
url: `http://127.0.0.1:${port}/mcp`,
|
|
2206
|
-
http_headers: { "X-Browser-Owner": owner },
|
|
2439
|
+
url: `http://127.0.0.1:${port}/mcp?${query2}`,
|
|
2207
2440
|
env_http_headers: { "X-Browser-Capability": CODEX_BROWSER_CAPABILITY_ENV }
|
|
2208
2441
|
};
|
|
2209
2442
|
}
|
|
2443
|
+
const query = new URLSearchParams({ owner });
|
|
2210
2444
|
if (agent === "maestro") {
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
};
|
|
2445
|
+
return buildStdioMcpServer("maestro", BROWSER_MCP_SSE_PROXY_SERVER, [], {
|
|
2446
|
+
NEGOTIUM_BROWSER_SSE_URL: `http://127.0.0.1:${port}/sse?${query}`,
|
|
2447
|
+
NEGOTIUM_BROWSER_OWNER_CAPABILITY: ownerCapability
|
|
2448
|
+
});
|
|
2216
2449
|
}
|
|
2217
2450
|
return {
|
|
2218
2451
|
type: "sse",
|
|
2219
|
-
url: `http://127.0.0.1:${port}/sse`,
|
|
2220
|
-
headers: { "X-Browser-
|
|
2452
|
+
url: `http://127.0.0.1:${port}/sse?${query}`,
|
|
2453
|
+
headers: { "X-Browser-Capability": ownerCapability }
|
|
2221
2454
|
};
|
|
2222
2455
|
}
|
|
2223
2456
|
function longLivedHttpMcp(agent, port) {
|
|
@@ -2610,175 +2843,6 @@ function getMcpServersForQuery(opts) {
|
|
|
2610
2843
|
});
|
|
2611
2844
|
}
|
|
2612
2845
|
|
|
2613
|
-
// ../../packages/core/src/storage/vault.ts
|
|
2614
|
-
import { chmodSync as chmodSync2, mkdirSync as mkdirSync7 } from "fs";
|
|
2615
|
-
import { join as join10 } from "path";
|
|
2616
|
-
|
|
2617
|
-
// ../../packages/core/src/storage/vault-crypto.ts
|
|
2618
|
-
import { createCipheriv, createDecipheriv, createHash, randomBytes as randomBytes4 } from "crypto";
|
|
2619
|
-
var ENVELOPE_PREFIX = "otium-vault:v1:";
|
|
2620
|
-
var IV_BYTES = 12;
|
|
2621
|
-
var KEY_BYTES = 32;
|
|
2622
|
-
function encryptionKey(masterKey = VAULT_MASTER_KEY) {
|
|
2623
|
-
return createHash("sha256").update("otium-vault-value-v1\x00", "utf8").update(masterKey, "utf8").digest().subarray(0, KEY_BYTES);
|
|
2624
|
-
}
|
|
2625
|
-
function aad(userId, key) {
|
|
2626
|
-
return Buffer.from(`${userId}\x00${key.toUpperCase()}`, "utf8");
|
|
2627
|
-
}
|
|
2628
|
-
function isEncryptedVaultValue(value) {
|
|
2629
|
-
return value.startsWith(ENVELOPE_PREFIX);
|
|
2630
|
-
}
|
|
2631
|
-
function encryptVaultValue(userId, key, value, masterKey = VAULT_MASTER_KEY) {
|
|
2632
|
-
const iv = randomBytes4(IV_BYTES);
|
|
2633
|
-
const cipher = createCipheriv("aes-256-gcm", encryptionKey(masterKey), iv);
|
|
2634
|
-
cipher.setAAD(aad(userId, key));
|
|
2635
|
-
const ciphertext = Buffer.concat([cipher.update(value, "utf8"), cipher.final()]);
|
|
2636
|
-
const tag = cipher.getAuthTag();
|
|
2637
|
-
return `${ENVELOPE_PREFIX}${iv.toString("base64url")}.${ciphertext.toString("base64url")}.${tag.toString("base64url")}`;
|
|
2638
|
-
}
|
|
2639
|
-
function decryptVaultValue(userId, key, storedValue, masterKey = VAULT_MASTER_KEY) {
|
|
2640
|
-
if (!isEncryptedVaultValue(storedValue)) {
|
|
2641
|
-
return { value: storedValue, legacyPlaintext: true };
|
|
2642
|
-
}
|
|
2643
|
-
const encoded = storedValue.slice(ENVELOPE_PREFIX.length);
|
|
2644
|
-
const [ivPart, ciphertextPart, tagPart, ...extra] = encoded.split(".");
|
|
2645
|
-
if (!ivPart || ciphertextPart === undefined || !tagPart || extra.length > 0) {
|
|
2646
|
-
throw new Error("Invalid encrypted vault value");
|
|
2647
|
-
}
|
|
2648
|
-
const iv = Buffer.from(ivPart, "base64url");
|
|
2649
|
-
const ciphertext = Buffer.from(ciphertextPart, "base64url");
|
|
2650
|
-
const tag = Buffer.from(tagPart, "base64url");
|
|
2651
|
-
if (iv.length !== IV_BYTES || tag.length !== 16) {
|
|
2652
|
-
throw new Error("Invalid encrypted vault value");
|
|
2653
|
-
}
|
|
2654
|
-
const decipher = createDecipheriv("aes-256-gcm", encryptionKey(masterKey), iv);
|
|
2655
|
-
decipher.setAAD(aad(userId, key));
|
|
2656
|
-
decipher.setAuthTag(tag);
|
|
2657
|
-
const plaintext = Buffer.concat([decipher.update(ciphertext), decipher.final()]);
|
|
2658
|
-
return { value: plaintext.toString("utf8"), legacyPlaintext: false };
|
|
2659
|
-
}
|
|
2660
|
-
|
|
2661
|
-
// ../../packages/core/src/storage/vault.ts
|
|
2662
|
-
var vaultDb;
|
|
2663
|
-
var vaultMasterKey = VAULT_MASTER_KEY;
|
|
2664
|
-
function initializeVaultDatabase(database) {
|
|
2665
|
-
database.exec("PRAGMA journal_mode = WAL");
|
|
2666
|
-
database.exec("PRAGMA busy_timeout = 5000");
|
|
2667
|
-
database.exec(`
|
|
2668
|
-
CREATE TABLE IF NOT EXISTS vault (
|
|
2669
|
-
user_id TEXT NOT NULL,
|
|
2670
|
-
key TEXT NOT NULL,
|
|
2671
|
-
value TEXT NOT NULL,
|
|
2672
|
-
description TEXT NOT NULL DEFAULT '',
|
|
2673
|
-
PRIMARY KEY (user_id, key)
|
|
2674
|
-
)
|
|
2675
|
-
`);
|
|
2676
|
-
{
|
|
2677
|
-
const cols = database.prepare("PRAGMA table_info(vault)").all();
|
|
2678
|
-
const uid = cols.find((c) => c.name === "user_id");
|
|
2679
|
-
if (uid && uid.type.toUpperCase() === "INTEGER") {
|
|
2680
|
-
database.exec("BEGIN");
|
|
2681
|
-
database.exec(`
|
|
2682
|
-
CREATE TABLE vault_migrated (
|
|
2683
|
-
user_id TEXT NOT NULL,
|
|
2684
|
-
key TEXT NOT NULL,
|
|
2685
|
-
value TEXT NOT NULL,
|
|
2686
|
-
description TEXT NOT NULL DEFAULT '',
|
|
2687
|
-
PRIMARY KEY (user_id, key)
|
|
2688
|
-
)
|
|
2689
|
-
`);
|
|
2690
|
-
database.exec("INSERT INTO vault_migrated SELECT CAST(user_id AS TEXT), key, value, description FROM vault");
|
|
2691
|
-
database.exec("DROP TABLE vault");
|
|
2692
|
-
database.exec("ALTER TABLE vault_migrated RENAME TO vault");
|
|
2693
|
-
database.exec("COMMIT");
|
|
2694
|
-
}
|
|
2695
|
-
}
|
|
2696
|
-
}
|
|
2697
|
-
function openVaultDatabase(dataDir) {
|
|
2698
|
-
const path = join10(dataDir, "vault.db");
|
|
2699
|
-
mkdirSync7(dataDir, { recursive: true });
|
|
2700
|
-
const database = new Database(path, { create: true });
|
|
2701
|
-
chmodSync2(path, 384);
|
|
2702
|
-
initializeVaultDatabase(database);
|
|
2703
|
-
return database;
|
|
2704
|
-
}
|
|
2705
|
-
function activeVaultDatabase() {
|
|
2706
|
-
if (!vaultDb)
|
|
2707
|
-
vaultDb = openVaultDatabase(DATA_DIR);
|
|
2708
|
-
return vaultDb;
|
|
2709
|
-
}
|
|
2710
|
-
var VAULT_VALUE_MAX_BYTES = 64 * 1024;
|
|
2711
|
-
function normalizeVaultKey(key) {
|
|
2712
|
-
return key.trim().toUpperCase();
|
|
2713
|
-
}
|
|
2714
|
-
function decryptRow(userId, key, storedValue) {
|
|
2715
|
-
const database = activeVaultDatabase();
|
|
2716
|
-
const decoded = decryptVaultValue(userId, key, storedValue, vaultMasterKey);
|
|
2717
|
-
if (decoded.legacyPlaintext) {
|
|
2718
|
-
database.prepare("UPDATE vault SET value = ? WHERE user_id = ? AND key = ? AND value = ?").run(encryptVaultValue(userId, key, decoded.value, vaultMasterKey), userId, key, storedValue);
|
|
2719
|
-
}
|
|
2720
|
-
return decoded.value;
|
|
2721
|
-
}
|
|
2722
|
-
function vaultListWithValues(userId) {
|
|
2723
|
-
const rows = activeVaultDatabase().prepare("SELECT key, description, value FROM vault WHERE user_id = ? ORDER BY key").all(userId);
|
|
2724
|
-
return rows.map((row) => ({
|
|
2725
|
-
key: row.key,
|
|
2726
|
-
description: row.description,
|
|
2727
|
-
value: decryptRow(userId, row.key, row.value)
|
|
2728
|
-
}));
|
|
2729
|
-
}
|
|
2730
|
-
function vaultSubstituteDetailed(userId, text) {
|
|
2731
|
-
const entries = new Map(vaultListWithValues(userId).map((entry) => [entry.key, entry.value]));
|
|
2732
|
-
const usedKeys = new Set;
|
|
2733
|
-
const substituted = text.replace(/\{\{([^}]+)\}\}/g, (match, rawKey) => {
|
|
2734
|
-
const key = normalizeVaultKey(rawKey);
|
|
2735
|
-
const value = entries.get(key);
|
|
2736
|
-
if (value === undefined)
|
|
2737
|
-
return match;
|
|
2738
|
-
usedKeys.add(key);
|
|
2739
|
-
return value;
|
|
2740
|
-
});
|
|
2741
|
-
return { text: substituted, usedKeys: [...usedKeys] };
|
|
2742
|
-
}
|
|
2743
|
-
function encodedSecretForms(value) {
|
|
2744
|
-
const forms = new Set([
|
|
2745
|
-
value,
|
|
2746
|
-
encodeURIComponent(value),
|
|
2747
|
-
Buffer.from(value, "utf8").toString("base64"),
|
|
2748
|
-
Buffer.from(value, "utf8").toString("base64url"),
|
|
2749
|
-
Buffer.from(value, "utf8").toString("hex")
|
|
2750
|
-
]);
|
|
2751
|
-
forms.delete("");
|
|
2752
|
-
return [...forms].sort((a, b) => b.length - a.length);
|
|
2753
|
-
}
|
|
2754
|
-
function redactVaultSecrets(userId, text) {
|
|
2755
|
-
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));
|
|
2756
|
-
if (candidates.length === 0)
|
|
2757
|
-
return text;
|
|
2758
|
-
const candidatesByFirstCharacter = new Map;
|
|
2759
|
-
for (const candidate of candidates) {
|
|
2760
|
-
const first = candidate.form[0];
|
|
2761
|
-
if (!first)
|
|
2762
|
-
continue;
|
|
2763
|
-
const bucket = candidatesByFirstCharacter.get(first) ?? [];
|
|
2764
|
-
bucket.push(candidate);
|
|
2765
|
-
candidatesByFirstCharacter.set(first, bucket);
|
|
2766
|
-
}
|
|
2767
|
-
let redacted = "";
|
|
2768
|
-
let offset = 0;
|
|
2769
|
-
while (offset < text.length) {
|
|
2770
|
-
const match = candidatesByFirstCharacter.get(text[offset] ?? "")?.find((candidate) => text.startsWith(candidate.form, offset));
|
|
2771
|
-
if (!match) {
|
|
2772
|
-
redacted += text[offset];
|
|
2773
|
-
offset += 1;
|
|
2774
|
-
continue;
|
|
2775
|
-
}
|
|
2776
|
-
redacted += `[REDACTED:${match.key}]`;
|
|
2777
|
-
offset += match.form.length;
|
|
2778
|
-
}
|
|
2779
|
-
return redacted;
|
|
2780
|
-
}
|
|
2781
|
-
|
|
2782
2846
|
// ../../packages/core/src/agents/execution-host.ts
|
|
2783
2847
|
var defaultHost = {
|
|
2784
2848
|
getMcpServersForQuery,
|
|
@@ -3218,12 +3282,6 @@ async function* claudeProvider(opts) {
|
|
|
3218
3282
|
const m = message;
|
|
3219
3283
|
if (m.subtype === "init") {
|
|
3220
3284
|
yield { type: "session", sessionId: m.session_id };
|
|
3221
|
-
} else if (m.subtype === "task_started") {
|
|
3222
|
-
const t = m;
|
|
3223
|
-
if (t.subagent_type && !t.skip_transcript) {
|
|
3224
|
-
const desc = t.description ? ` ${t.description.slice(0, 60)}` : "";
|
|
3225
|
-
yield { type: "status", content: `\u25B6 [${t.subagent_type}]${desc}` };
|
|
3226
|
-
}
|
|
3227
3285
|
} else {
|
|
3228
3286
|
logger.debug({ subtype: m.subtype, msg: m }, "claudeProvider: system message (unhandled subtype)");
|
|
3229
3287
|
}
|
|
@@ -3326,27 +3384,84 @@ import { Codex } from "@openai/codex-sdk";
|
|
|
3326
3384
|
|
|
3327
3385
|
// ../../packages/core/src/agents/codex-native-multi-agent.ts
|
|
3328
3386
|
import { spawn as spawn3 } from "child_process";
|
|
3387
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
3329
3388
|
import {
|
|
3330
3389
|
chmodSync as chmodSync3,
|
|
3390
|
+
copyFileSync,
|
|
3331
3391
|
existsSync as existsSync9,
|
|
3392
|
+
mkdtempSync,
|
|
3332
3393
|
readFileSync as readFileSync7,
|
|
3333
3394
|
renameSync as renameSync4,
|
|
3395
|
+
rmSync,
|
|
3334
3396
|
unlinkSync as unlinkSync8,
|
|
3335
3397
|
writeFileSync as writeFileSync5
|
|
3336
3398
|
} from "fs";
|
|
3337
3399
|
import { createRequire } from "module";
|
|
3400
|
+
import { tmpdir } from "os";
|
|
3338
3401
|
import { dirname as dirname6, join as join11 } from "path";
|
|
3339
3402
|
|
|
3340
3403
|
// ../../packages/core/src/version.ts
|
|
3341
|
-
var NEGOTIUM_VERSION = "0.1.
|
|
3404
|
+
var NEGOTIUM_VERSION = "0.1.29";
|
|
3342
3405
|
|
|
3343
3406
|
// ../../packages/core/src/agents/codex-native-multi-agent.ts
|
|
3344
|
-
var NEGOTIUM_MODEL_CATALOG = "negotium-model-catalog.json";
|
|
3345
3407
|
var moduleRequire = createRequire(import.meta.url);
|
|
3408
|
+
var codexSdkPackagePath = moduleRequire.resolve("@openai/codex-sdk/package.json");
|
|
3409
|
+
var codexSdkRequire = createRequire(codexSdkPackagePath);
|
|
3410
|
+
var bundledCodexPackagePath = codexSdkRequire.resolve("@openai/codex/package.json");
|
|
3411
|
+
function readPackageVersion(packageJsonPath) {
|
|
3412
|
+
const parsed = JSON.parse(readFileSync7(packageJsonPath, "utf8"));
|
|
3413
|
+
if (typeof parsed.version !== "string" || !parsed.version.trim()) {
|
|
3414
|
+
throw new Error(`Codex package has no valid version: ${packageJsonPath}`);
|
|
3415
|
+
}
|
|
3416
|
+
return parsed.version;
|
|
3417
|
+
}
|
|
3418
|
+
var BUNDLED_CODEX_VERSION = readPackageVersion(bundledCodexPackagePath);
|
|
3419
|
+
var SAFE_BUNDLED_CODEX_VERSION = BUNDLED_CODEX_VERSION.replace(/[^a-zA-Z0-9._-]/g, "_");
|
|
3420
|
+
var NEGOTIUM_MODEL_CACHE = `negotium-models-cache-${SAFE_BUNDLED_CODEX_VERSION}.json`;
|
|
3421
|
+
var NEGOTIUM_MODEL_CATALOG = `negotium-model-catalog-${SAFE_BUNDLED_CODEX_VERSION}.json`;
|
|
3346
3422
|
function codexCliScriptPath() {
|
|
3347
|
-
|
|
3348
|
-
|
|
3349
|
-
|
|
3423
|
+
return join11(dirname6(bundledCodexPackagePath), "bin", "codex.js");
|
|
3424
|
+
}
|
|
3425
|
+
function parseCodexModelCache(contents, sourcePath) {
|
|
3426
|
+
let parsed;
|
|
3427
|
+
try {
|
|
3428
|
+
parsed = JSON.parse(contents);
|
|
3429
|
+
} catch (error) {
|
|
3430
|
+
throw new Error(`Codex model cache is invalid JSON: ${sourcePath}`, { cause: error });
|
|
3431
|
+
}
|
|
3432
|
+
if (!Array.isArray(parsed.models) || parsed.models.length === 0) {
|
|
3433
|
+
throw new Error(`Codex model cache has no models: ${sourcePath}`);
|
|
3434
|
+
}
|
|
3435
|
+
return parsed;
|
|
3436
|
+
}
|
|
3437
|
+
function readCodexModelCache(cachePath) {
|
|
3438
|
+
const contents = readFileSync7(cachePath, "utf8");
|
|
3439
|
+
return { contents, parsed: parseCodexModelCache(contents, cachePath) };
|
|
3440
|
+
}
|
|
3441
|
+
function readCompatibleCodexModelCache(cachePath) {
|
|
3442
|
+
const cache = readCodexModelCache(cachePath);
|
|
3443
|
+
if (cache.parsed.client_version !== BUNDLED_CODEX_VERSION) {
|
|
3444
|
+
const found = typeof cache.parsed.client_version === "string" ? cache.parsed.client_version : "missing or invalid";
|
|
3445
|
+
throw new Error(`Codex model cache version ${found} does not match Negotium's bundled Codex ${BUNDLED_CODEX_VERSION}: ${cachePath}`);
|
|
3446
|
+
}
|
|
3447
|
+
return cache;
|
|
3448
|
+
}
|
|
3449
|
+
function writePrivateFileAtomic(path, contents) {
|
|
3450
|
+
if (existsSync9(path) && readFileSync7(path, "utf8") === contents)
|
|
3451
|
+
return;
|
|
3452
|
+
const tempPath = `${path}.${process.pid}.${randomUUID2()}.tmp`;
|
|
3453
|
+
try {
|
|
3454
|
+
writeFileSync5(tempPath, contents, { encoding: "utf8", mode: 384 });
|
|
3455
|
+
renameSync4(tempPath, path);
|
|
3456
|
+
chmodSync3(path, 384);
|
|
3457
|
+
} finally {
|
|
3458
|
+
try {
|
|
3459
|
+
unlinkSync8(tempPath);
|
|
3460
|
+
} catch {}
|
|
3461
|
+
}
|
|
3462
|
+
}
|
|
3463
|
+
function bundledCodexModelCachePath(authFilePath) {
|
|
3464
|
+
return join11(dirname6(authFilePath), NEGOTIUM_MODEL_CACHE);
|
|
3350
3465
|
}
|
|
3351
3466
|
async function bootstrapCodexModelCache(codexHome, cachePath) {
|
|
3352
3467
|
const child = spawn3(process.execPath, [codexCliScriptPath(), "app-server", "--stdio"], {
|
|
@@ -3430,25 +3545,59 @@ async function bootstrapCodexModelCache(codexHome, cachePath) {
|
|
|
3430
3545
|
});
|
|
3431
3546
|
});
|
|
3432
3547
|
}
|
|
3548
|
+
async function bootstrapIsolatedCodexModelCache(authFilePath, bootstrap) {
|
|
3549
|
+
const sourceHome = dirname6(authFilePath);
|
|
3550
|
+
const isolatedHome = mkdtempSync(join11(tmpdir(), "negotium-codex-models-"));
|
|
3551
|
+
const isolatedCachePath = join11(isolatedHome, "models_cache.json");
|
|
3552
|
+
try {
|
|
3553
|
+
const isolatedAuthPath = join11(isolatedHome, "auth.json");
|
|
3554
|
+
copyFileSync(authFilePath, isolatedAuthPath);
|
|
3555
|
+
chmodSync3(isolatedAuthPath, 384);
|
|
3556
|
+
const sourceConfigPath = join11(sourceHome, "config.toml");
|
|
3557
|
+
if (existsSync9(sourceConfigPath)) {
|
|
3558
|
+
const isolatedConfigPath = join11(isolatedHome, "config.toml");
|
|
3559
|
+
copyFileSync(sourceConfigPath, isolatedConfigPath);
|
|
3560
|
+
chmodSync3(isolatedConfigPath, 384);
|
|
3561
|
+
}
|
|
3562
|
+
await bootstrap(isolatedHome, isolatedCachePath);
|
|
3563
|
+
return readCompatibleCodexModelCache(isolatedCachePath).contents;
|
|
3564
|
+
} finally {
|
|
3565
|
+
rmSync(isolatedHome, { recursive: true, force: true });
|
|
3566
|
+
}
|
|
3567
|
+
}
|
|
3433
3568
|
async function ensureCodexModelCache(authFilePath, bootstrap = bootstrapCodexModelCache) {
|
|
3434
3569
|
const codexHome = dirname6(authFilePath);
|
|
3435
|
-
const
|
|
3436
|
-
|
|
3437
|
-
|
|
3438
|
-
|
|
3439
|
-
|
|
3440
|
-
|
|
3570
|
+
const configuredCachePath = process.env.NEGOTIUM_CODEX_MODELS_CACHE_FILE;
|
|
3571
|
+
if (configuredCachePath) {
|
|
3572
|
+
if (!existsSync9(configuredCachePath)) {
|
|
3573
|
+
throw new Error(`Configured Codex model cache does not exist: ${configuredCachePath}`);
|
|
3574
|
+
}
|
|
3575
|
+
readCompatibleCodexModelCache(configuredCachePath);
|
|
3576
|
+
return configuredCachePath;
|
|
3577
|
+
}
|
|
3578
|
+
const bundledCachePath = bundledCodexModelCachePath(authFilePath);
|
|
3579
|
+
const sharedCachePath = join11(codexHome, "models_cache.json");
|
|
3580
|
+
if (existsSync9(sharedCachePath)) {
|
|
3581
|
+
try {
|
|
3582
|
+
const shared = readCompatibleCodexModelCache(sharedCachePath);
|
|
3583
|
+
writePrivateFileAtomic(bundledCachePath, shared.contents);
|
|
3584
|
+
return bundledCachePath;
|
|
3585
|
+
} catch {}
|
|
3441
3586
|
}
|
|
3442
|
-
|
|
3587
|
+
if (existsSync9(bundledCachePath)) {
|
|
3588
|
+
try {
|
|
3589
|
+
readCompatibleCodexModelCache(bundledCachePath);
|
|
3590
|
+
return bundledCachePath;
|
|
3591
|
+
} catch {}
|
|
3592
|
+
}
|
|
3593
|
+
const refreshedContents = await bootstrapIsolatedCodexModelCache(authFilePath, bootstrap);
|
|
3594
|
+
writePrivateFileAtomic(bundledCachePath, refreshedContents);
|
|
3595
|
+
return bundledCachePath;
|
|
3443
3596
|
}
|
|
3444
|
-
function writeCodexCatalogWithNativeMultiAgentDisabled(authFilePath) {
|
|
3597
|
+
function writeCodexCatalogWithNativeMultiAgentDisabled(authFilePath, sourcePath) {
|
|
3445
3598
|
const codexHome = dirname6(authFilePath);
|
|
3446
|
-
const sourcePath = process.env.NEGOTIUM_CODEX_MODELS_CACHE_FILE ?? (existsSync9(join11(codexHome, "models_cache.json")) ? join11(codexHome, "models_cache.json") : join11(codexHome, NEGOTIUM_MODEL_CATALOG));
|
|
3447
3599
|
const outputPath = join11(codexHome, NEGOTIUM_MODEL_CATALOG);
|
|
3448
|
-
const parsed =
|
|
3449
|
-
if (!Array.isArray(parsed.models) || parsed.models.length === 0) {
|
|
3450
|
-
throw new Error(`Codex model cache has no models: ${sourcePath}`);
|
|
3451
|
-
}
|
|
3600
|
+
const parsed = readCodexModelCache(sourcePath).parsed;
|
|
3452
3601
|
const models = parsed.models.map((model, index) => {
|
|
3453
3602
|
if (!model || typeof model !== "object" || Array.isArray(model)) {
|
|
3454
3603
|
throw new Error(`Codex model cache entry ${index} is invalid: ${sourcePath}`);
|
|
@@ -3457,19 +3606,7 @@ function writeCodexCatalogWithNativeMultiAgentDisabled(authFilePath) {
|
|
|
3457
3606
|
});
|
|
3458
3607
|
const contents = `${JSON.stringify({ models }, null, 2)}
|
|
3459
3608
|
`;
|
|
3460
|
-
|
|
3461
|
-
return outputPath;
|
|
3462
|
-
}
|
|
3463
|
-
const tempPath = `${outputPath}.${process.pid}.${Date.now()}.tmp`;
|
|
3464
|
-
try {
|
|
3465
|
-
writeFileSync5(tempPath, contents, { encoding: "utf8", mode: 384 });
|
|
3466
|
-
renameSync4(tempPath, outputPath);
|
|
3467
|
-
chmodSync3(outputPath, 384);
|
|
3468
|
-
} finally {
|
|
3469
|
-
try {
|
|
3470
|
-
unlinkSync8(tempPath);
|
|
3471
|
-
} catch {}
|
|
3472
|
-
}
|
|
3609
|
+
writePrivateFileAtomic(outputPath, contents);
|
|
3473
3610
|
return outputPath;
|
|
3474
3611
|
}
|
|
3475
3612
|
|
|
@@ -3666,8 +3803,8 @@ async function* codexProvider(opts) {
|
|
|
3666
3803
|
}
|
|
3667
3804
|
let codexModelCatalogPath;
|
|
3668
3805
|
try {
|
|
3669
|
-
await ensureCodexModelCache(codexAuthPath);
|
|
3670
|
-
codexModelCatalogPath = writeCodexCatalogWithNativeMultiAgentDisabled(codexAuthPath);
|
|
3806
|
+
const codexModelCachePath = await ensureCodexModelCache(codexAuthPath);
|
|
3807
|
+
codexModelCatalogPath = writeCodexCatalogWithNativeMultiAgentDisabled(codexAuthPath, codexModelCachePath);
|
|
3671
3808
|
if (opts.sessionId)
|
|
3672
3809
|
migrateCodexRolloutNativeMultiAgentMetadata(opts.sessionId);
|
|
3673
3810
|
} catch (err) {
|
|
@@ -4000,6 +4137,18 @@ function providerOwnedToolRedirect(toolName) {
|
|
|
4000
4137
|
}
|
|
4001
4138
|
return "Use the shared task MCP tools instead " + "(mcp__task__task_create / task_update / task_list / task_get / task_delete).";
|
|
4002
4139
|
}
|
|
4140
|
+
function resolveMaestroApiKeyOverrides(userId) {
|
|
4141
|
+
if (!userId)
|
|
4142
|
+
return;
|
|
4143
|
+
const deepseek = vaultGetValue(userId, "DEEPSEEK_API_KEY")?.trim();
|
|
4144
|
+
const moonshot = vaultGetValue(userId, "MOONSHOT_API_KEY")?.trim();
|
|
4145
|
+
if (!deepseek && !moonshot)
|
|
4146
|
+
return;
|
|
4147
|
+
return {
|
|
4148
|
+
...deepseek ? { deepseek } : {},
|
|
4149
|
+
...moonshot ? { moonshot } : {}
|
|
4150
|
+
};
|
|
4151
|
+
}
|
|
4003
4152
|
function buildProviderOwnedToolBlockHook() {
|
|
4004
4153
|
return {
|
|
4005
4154
|
name: "provider-owned-tool-redirect",
|
|
@@ -4026,6 +4175,7 @@ function maestroProvider(opts) {
|
|
|
4026
4175
|
maxTokens: MAESTRO_DEFAULT_MAX_TOKENS,
|
|
4027
4176
|
enableToolSearch: true,
|
|
4028
4177
|
...opts,
|
|
4178
|
+
apiKeyOverrides: resolveMaestroApiKeyOverrides(userId),
|
|
4029
4179
|
agent: "maestro",
|
|
4030
4180
|
disallowedTools: buildMaestroDisallowedTools(callerDisallowedTools),
|
|
4031
4181
|
toolHooks: [...buildMaestroToolHooks(userId), ...callerToolHooks]
|
|
@@ -4207,7 +4357,7 @@ async function* runAgent(opts) {
|
|
|
4207
4357
|
}
|
|
4208
4358
|
|
|
4209
4359
|
// ../../packages/core/src/bus.ts
|
|
4210
|
-
import { randomUUID as
|
|
4360
|
+
import { randomUUID as randomUUID3 } from "crypto";
|
|
4211
4361
|
|
|
4212
4362
|
// ../../packages/core/src/storage/forum-db.ts
|
|
4213
4363
|
var db = internalStorageDatabase;
|
|
@@ -4288,7 +4438,7 @@ class SqliteRuntimeBus {
|
|
|
4288
4438
|
pollTimer = null;
|
|
4289
4439
|
polling = false;
|
|
4290
4440
|
constructor(options = {}) {
|
|
4291
|
-
this.sourceId = options.sourceId ?? `${process.pid}-${
|
|
4441
|
+
this.sourceId = options.sourceId ?? `${process.pid}-${randomUUID3()}`;
|
|
4292
4442
|
this.pollIntervalMs = Math.max(25, options.pollIntervalMs ?? 100);
|
|
4293
4443
|
this.cursor = latestRuntimeEventSeq();
|
|
4294
4444
|
}
|
|
@@ -4741,7 +4891,7 @@ function wikiSummaryFilename(date, rawTopic, topicId) {
|
|
|
4741
4891
|
}
|
|
4742
4892
|
|
|
4743
4893
|
// ../../packages/core/src/topics/personal-general.ts
|
|
4744
|
-
import { randomUUID as
|
|
4894
|
+
import { randomUUID as randomUUID4 } from "crypto";
|
|
4745
4895
|
|
|
4746
4896
|
// ../../packages/core/src/platform/constants.ts
|
|
4747
4897
|
var FROM_AUTO_CONTINUE = "auto-continue";
|
|
@@ -5250,7 +5400,7 @@ function ensurePersonalGeneral(userId) {
|
|
|
5250
5400
|
const now = new Date().toISOString();
|
|
5251
5401
|
const registry = getRegistry("maestro");
|
|
5252
5402
|
const topic = {
|
|
5253
|
-
id:
|
|
5403
|
+
id: randomUUID4(),
|
|
5254
5404
|
title: "General",
|
|
5255
5405
|
description: PERSONAL_GENERAL_DESCRIPTION,
|
|
5256
5406
|
kind: "manager",
|
|
@@ -5336,7 +5486,7 @@ function runArchiverTurn(params) {
|
|
|
5336
5486
|
mcpEnabled: ["wiki"],
|
|
5337
5487
|
silent: true
|
|
5338
5488
|
});
|
|
5339
|
-
const activeSessionId = `memory:${
|
|
5489
|
+
const activeSessionId = `memory:${randomUUID5()}`;
|
|
5340
5490
|
activeArchiverSessions.set(activeSessionId, {
|
|
5341
5491
|
id: activeSessionId,
|
|
5342
5492
|
kind: "memory",
|
|
@@ -5492,7 +5642,7 @@ ${rolled.join(`
|
|
|
5492
5642
|
usage: generalReply.usage
|
|
5493
5643
|
} : { authorId: "system" };
|
|
5494
5644
|
const msg = {
|
|
5495
|
-
id:
|
|
5645
|
+
id: randomUUID5(),
|
|
5496
5646
|
topicId: generalTopicId,
|
|
5497
5647
|
text,
|
|
5498
5648
|
...replyMeta,
|
|
@@ -5526,7 +5676,7 @@ function countMemoryArchiveExchanges(rows) {
|
|
|
5526
5676
|
}
|
|
5527
5677
|
|
|
5528
5678
|
// ../../packages/core/src/storage/runtime-leases.ts
|
|
5529
|
-
import { randomUUID as
|
|
5679
|
+
import { randomUUID as randomUUID6 } from "crypto";
|
|
5530
5680
|
|
|
5531
5681
|
// ../../packages/core/src/storage/runtime-topic-state.ts
|
|
5532
5682
|
var TOPIC_MAINTENANCE_STALE_MS = 30000;
|
|
@@ -5541,7 +5691,7 @@ db.exec(`
|
|
|
5541
5691
|
`);
|
|
5542
5692
|
|
|
5543
5693
|
// ../../packages/core/src/storage/runtime-leases.ts
|
|
5544
|
-
var RUNTIME_INSTANCE_ID = `${process.pid}-${
|
|
5694
|
+
var RUNTIME_INSTANCE_ID = `${process.pid}-${randomUUID6()}`;
|
|
5545
5695
|
var TURN_LEASE_STALE_MS = 1e4;
|
|
5546
5696
|
db.exec(`
|
|
5547
5697
|
CREATE TABLE IF NOT EXISTS runtime_turn_leases (
|
|
@@ -6020,7 +6170,7 @@ function archiveTopicMessages(topicId, topicTitle, options = {}) {
|
|
|
6020
6170
|
}
|
|
6021
6171
|
|
|
6022
6172
|
// ../../packages/core/src/storage/topic-archive-state.ts
|
|
6023
|
-
import { rmSync } from "fs";
|
|
6173
|
+
import { rmSync as rmSync2 } from "fs";
|
|
6024
6174
|
registerStorageSchemaInitializer((database) => {
|
|
6025
6175
|
database.exec(`
|
|
6026
6176
|
CREATE TABLE IF NOT EXISTS api_topic_archive_state (
|
|
@@ -6121,7 +6271,7 @@ function claimTopicArchiveJob(topicId, create, now = Date.now()) {
|
|
|
6121
6271
|
})();
|
|
6122
6272
|
if (result.kind === "claimed")
|
|
6123
6273
|
return result;
|
|
6124
|
-
|
|
6274
|
+
rmSync2(candidate.archivePath, { force: true });
|
|
6125
6275
|
if (result.kind === "busy")
|
|
6126
6276
|
return result;
|
|
6127
6277
|
}
|
|
@@ -6417,4 +6567,4 @@ export {
|
|
|
6417
6567
|
MIN_MEMORY_ARCHIVE_EXCHANGES
|
|
6418
6568
|
};
|
|
6419
6569
|
|
|
6420
|
-
//# debugId=
|
|
6570
|
+
//# debugId=A260F0394930433E64756E2164756E21
|