negotium 0.1.49 → 0.2.0

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.
Files changed (50) hide show
  1. package/README.md +1 -1
  2. package/dist/agent-helpers.js +58 -74
  3. package/dist/agent-helpers.js.map +15 -15
  4. package/dist/background-bash.js +22 -10
  5. package/dist/background-bash.js.map +3 -3
  6. package/dist/browser-runtime.js +35 -68
  7. package/dist/browser-runtime.js.map +7 -7
  8. package/dist/{chunk-5rpw9d1f.js → chunk-kc5kmr95.js} +28 -11
  9. package/dist/{chunk-5rpw9d1f.js.map → chunk-kc5kmr95.js.map} +4 -4
  10. package/dist/hosted-agent.js +36 -18
  11. package/dist/hosted-agent.js.map +7 -7
  12. package/dist/main.js +562 -98
  13. package/dist/main.js.map +21 -19
  14. package/dist/mcp-factories.js +62 -78
  15. package/dist/mcp-factories.js.map +16 -16
  16. package/dist/prompts.js +22 -10
  17. package/dist/prompts.js.map +3 -3
  18. package/dist/query-runtime.js +22 -10
  19. package/dist/query-runtime.js.map +4 -4
  20. package/dist/registry.js +5 -12
  21. package/dist/registry.js.map +5 -5
  22. package/dist/rollout.js +1 -1
  23. package/dist/runtime/src/agents/maestro-registry.ts +1 -1
  24. package/dist/runtime/src/agents/rollout/shared.ts +12 -4
  25. package/dist/runtime/src/mcp/mcp-helpers.ts +1 -1
  26. package/dist/runtime/src/mcp/wiki-server.ts +1 -1
  27. package/dist/runtime/src/migration/single-user.ts +621 -0
  28. package/dist/runtime/src/platform/config.ts +24 -11
  29. package/dist/runtime/src/platform/mcp-config.ts +4 -4
  30. package/dist/runtime/src/platform/playwright/manager.ts +10 -28
  31. package/dist/runtime/src/runtime/attachments.ts +3 -3
  32. package/dist/runtime/src/storage/browser-profiles.ts +11 -9
  33. package/dist/runtime/src/storage/conversations.ts +5 -18
  34. package/dist/runtime/src/storage/storage-host.ts +1 -1
  35. package/dist/runtime/src/storage/tasks.ts +2 -14
  36. package/dist/runtime/src/storage/vault.ts +3 -2
  37. package/dist/runtime/src/version.ts +1 -1
  38. package/dist/runtime-helpers.js +22 -10
  39. package/dist/runtime-helpers.js.map +5 -5
  40. package/dist/storage.js +5 -19
  41. package/dist/storage.js.map +5 -5
  42. package/dist/types/packages/core/src/mcp/mcp-helpers.d.ts +1 -1
  43. package/dist/types/packages/core/src/platform/config.d.ts +9 -1
  44. package/dist/types/packages/core/src/runtime/attachments.d.ts +1 -1
  45. package/dist/types/packages/core/src/storage/conversations.d.ts +2 -2
  46. package/dist/types/packages/core/src/version.d.ts +1 -1
  47. package/dist/vault.js +25 -12
  48. package/dist/vault.js.map +4 -4
  49. package/install-browser-rs.mjs +1 -1
  50. package/package.json +2 -2
package/dist/main.js CHANGED
@@ -168,7 +168,7 @@ function resolveBrowserRsBin(envValue) {
168
168
  if (!override && !versionAtLeast(BROWSER_RS_VERSION.replace(/^v/, ""), BROWSER_RS_MIN_SECURE_VERSION)) {
169
169
  return;
170
170
  }
171
- const candidate = override ? resolve(override) : resolve(STATE_DIR, "bin", "browser-rs", BROWSER_RS_VERSION, "browser-rs");
171
+ const candidate = override ? resolve(override) : resolve(BINARIES_DIR, "browser-rs", BROWSER_RS_VERSION, "browser-rs");
172
172
  try {
173
173
  accessSync(candidate, constants.X_OK);
174
174
  return browserRsMeetsMinimumVersion(candidate) ? candidate : undefined;
@@ -180,7 +180,7 @@ function resolveBrowserMcpBin(envValue) {
180
180
  const override = envValue?.trim();
181
181
  if (override)
182
182
  return resolve(override);
183
- return BROWSER_RS_BIN ?? resolve(STATE_DIR, "bin", "browser-rs", BROWSER_RS_VERSION, "browser-rs");
183
+ return BROWSER_RS_BIN ?? resolve(BINARIES_DIR, "browser-rs", BROWSER_RS_VERSION, "browser-rs");
184
184
  }
185
185
  function resolveBrowserProxy() {
186
186
  const raw = envText("BROWSER_PROXY_URL");
@@ -212,7 +212,7 @@ function resolveTopicWorkspaceDir(topicId) {
212
212
  }
213
213
  function loadOrCreateLocalSecret(envKey, filename, options = {}) {
214
214
  const envValue = envText(envKey);
215
- const secretFile = resolve(STATE_DIR, filename);
215
+ const secretFile = resolve(SECRETS_DIR, filename);
216
216
  mkdirSync(dirname(secretFile), { recursive: true });
217
217
  if (envValue) {
218
218
  if (options.persistEnvValue) {
@@ -253,7 +253,7 @@ function resolveDefaultModel(agent, registryDefaultModel) {
253
253
  function codexAuthFilePath() {
254
254
  return process.env.NEGOTIUM_CODEX_AUTH_FILE || join(homedir(), ".codex", "auth.json");
255
255
  }
256
- var HOME, PROJECT_ROOT, STATE_DIR_ENV, STATE_DIR, WORKSPACE_DIR, TOPIC_WORKSPACE_DIR, SHARED_WIKI_DIR, CONTEXTS_DIR, BROWSER_PROFILES_DIR, DM_WORKSPACE_DIR, SESSION_WORKSPACE_DIR, CLAUDE_EXECUTABLE_ENV, CLAUDE_EXECUTABLE, DEFAULT_OUTPUT_LANGUAGE = "English", BROWSER_RS_VERSION = "v0.1.16", BROWSER_RS_MIN_SECURE_VERSION = "0.1.15", BROWSER_RS_BIN, PLAYWRIGHT_MCP_BIN, TSX_BIN, TSX_LOADER, TSCONFIG_PATH, SESSION_COMM_SERVER, TASK_SERVER, BROWSER_MCP_SSE_PROXY_SERVER, CANONICAL_MCP_PROXY_SERVER, WIKI_SERVER, TOKEN_STATS_SERVER, COMPACTION_LOG_SERVER, SYSTEM_HEALTH_SERVER, AGENT_HEALTH_SERVER, BACKGROUND_BASH_SERVER, VAULT_SERVER, BG_BASH_BASE_PORT, BG_BASH_MAX_PORT, RUNTIME_MCP_SECRET, NODE_CONTROL_TOKEN, VAULT_MASTER_KEY, NEGOTIUM_PORT, hostname, DATA_DIR, LOG_DIR, SESSIONS_DB, DEBUG_FILE, USERS_LOG_DIR, RUN_DIR, PROGRESS_DIR, DM_CMD_DIR, DM_RESP_DIR, SESSION_INBOX_DIR, SESSION_ASKS_DIR, PLAYWRIGHT_BASE_PORT, PLAYWRIGHT_MAX_PORT, PLAYWRIGHT_PORTS_DIR, ACTIVE_QUERY_STALE_MS, AGENTS_PROMPTS_DIR, RESOURCES_DIR, FILE_TAG_REGEX, MODEL_SONNET = "claude-sonnet-5", MODEL_OPUS = "claude-opus-5", MODEL_FABLE = "claude-fable-5", FALLBACK_AGENT, SESSION_AGENT, GATEWAY_AGENT, FALLBACK_MODEL, SESSION_MODEL, GATEWAY_MODEL, FFMPEG_BIN, FFPROBE_BIN, PYTHON_BIN, FASTER_WHISPER_WRAPPER, WHISPER_MODEL, TESSERACT_BIN, PDFTOTEXT_BIN, _envMaxTellDepth, MAX_TELL_DEPTH;
256
+ var HOME, PROJECT_ROOT, STATE_DIR_ENV, STATE_DIR, WORKSPACE_DIR, TOPIC_WORKSPACE_DIR, SHARED_WIKI_DIR, CRON_WORKSPACE_DIR, BROWSER_DIR, BROWSER_PROFILES_DIR, BINARIES_DIR, SECRETS_DIR, CONTEXTS_DIR, DM_WORKSPACE_DIR, SESSION_WORKSPACE_DIR, CLAUDE_EXECUTABLE_ENV, CLAUDE_EXECUTABLE, DEFAULT_OUTPUT_LANGUAGE = "English", BROWSER_RS_VERSION = "v0.1.16", BROWSER_RS_MIN_SECURE_VERSION = "0.1.15", BROWSER_RS_BIN, PLAYWRIGHT_MCP_BIN, TSX_BIN, TSX_LOADER, TSCONFIG_PATH, SESSION_COMM_SERVER, TASK_SERVER, BROWSER_MCP_SSE_PROXY_SERVER, CANONICAL_MCP_PROXY_SERVER, WIKI_SERVER, TOKEN_STATS_SERVER, COMPACTION_LOG_SERVER, SYSTEM_HEALTH_SERVER, AGENT_HEALTH_SERVER, BACKGROUND_BASH_SERVER, VAULT_SERVER, BG_BASH_BASE_PORT, BG_BASH_MAX_PORT, RUNTIME_MCP_SECRET, NODE_CONTROL_TOKEN, VAULT_MASTER_KEY, NEGOTIUM_PORT, hostname, DATA_DIR, LOG_DIR, UPLOADS_DIR, VAULT_DIR, SESSIONS_DB, DEBUG_FILE, USERS_LOG_DIR, RUN_DIR, PROGRESS_DIR, DM_CMD_DIR, DM_RESP_DIR, SESSION_INBOX_DIR, SESSION_ASKS_DIR, PLAYWRIGHT_BASE_PORT, PLAYWRIGHT_MAX_PORT, PLAYWRIGHT_PORTS_DIR, ACTIVE_QUERY_STALE_MS, AGENTS_PROMPTS_DIR, RESOURCES_DIR, FILE_TAG_REGEX, MODEL_SONNET = "claude-sonnet-5", MODEL_OPUS = "claude-opus-5", MODEL_FABLE = "claude-fable-5", FALLBACK_AGENT, SESSION_AGENT, GATEWAY_AGENT, FALLBACK_MODEL, SESSION_MODEL, GATEWAY_MODEL, FFMPEG_BIN, FFPROBE_BIN, PYTHON_BIN, FASTER_WHISPER_WRAPPER, WHISPER_MODEL, TESSERACT_BIN, PDFTOTEXT_BIN, _envMaxTellDepth, MAX_TELL_DEPTH;
257
257
  var init_config = __esm(() => {
258
258
  init_config_helpers();
259
259
  init_logger();
@@ -265,10 +265,14 @@ var init_config = __esm(() => {
265
265
  WORKSPACE_DIR = resolveLocalStateDir("NEGOTIUM_WORKSPACE_DIR", "workspace");
266
266
  TOPIC_WORKSPACE_DIR = resolve(WORKSPACE_DIR, "topics");
267
267
  SHARED_WIKI_DIR = resolve(WORKSPACE_DIR, "wiki");
268
- CONTEXTS_DIR = resolve(WORKSPACE_DIR, "contexts");
269
- BROWSER_PROFILES_DIR = resolve(WORKSPACE_DIR, "browser-profiles");
270
- DM_WORKSPACE_DIR = resolve(WORKSPACE_DIR, "dm");
271
- SESSION_WORKSPACE_DIR = resolve(WORKSPACE_DIR, "sessions");
268
+ CRON_WORKSPACE_DIR = resolve(WORKSPACE_DIR, "cron");
269
+ BROWSER_DIR = resolveLocalStateDir("NEGOTIUM_BROWSER_DIR", "browser");
270
+ BROWSER_PROFILES_DIR = resolve(BROWSER_DIR, "profiles");
271
+ BINARIES_DIR = resolve(STATE_DIR, "binaries");
272
+ SECRETS_DIR = resolve(STATE_DIR, "secrets");
273
+ CONTEXTS_DIR = resolve(STATE_DIR, "data", "contexts");
274
+ DM_WORKSPACE_DIR = resolve(STATE_DIR, "data", "dm");
275
+ SESSION_WORKSPACE_DIR = resolve(STATE_DIR, "data", "sessions");
272
276
  CLAUDE_EXECUTABLE_ENV = envText("NEGOTIUM_CLAUDE_EXECUTABLE");
273
277
  CLAUDE_EXECUTABLE = CLAUDE_EXECUTABLE_ENV ? resolve(CLAUDE_EXECUTABLE_ENV) : undefined;
274
278
  BROWSER_RS_BIN = resolveBrowserRsBin(envText("NEGOTIUM_BROWSER_RS_BIN"));
@@ -297,10 +301,12 @@ var init_config = __esm(() => {
297
301
  hostname = process.env.HOSTNAME || "127.0.0.1";
298
302
  DATA_DIR = resolveLocalStateDir("NEGOTIUM_DATA_DIR", "data");
299
303
  LOG_DIR = resolveLocalStateDir("NEGOTIUM_LOG_DIR", "logs");
304
+ UPLOADS_DIR = resolve(DATA_DIR, "uploads");
305
+ VAULT_DIR = resolve(DATA_DIR, "vault");
300
306
  SESSIONS_DB = process.env.SESSIONS_DB_PATH ? resolve(process.env.SESSIONS_DB_PATH) : resolve(DATA_DIR, "sessions.db");
301
307
  DEBUG_FILE = resolve(DATA_DIR, "debug-users.json");
302
308
  USERS_LOG_DIR = resolve(DATA_DIR, "users");
303
- RUN_DIR = resolveLocalStateDir("NEGOTIUM_RUN_DIR", "run");
309
+ RUN_DIR = resolveLocalStateDir("NEGOTIUM_RUN_DIR", "runtime");
304
310
  PROGRESS_DIR = resolve(RUN_DIR, "progress");
305
311
  DM_CMD_DIR = resolve(RUN_DIR, "dm-commands");
306
312
  DM_RESP_DIR = resolve(RUN_DIR, "dm-responses");
@@ -311,6 +317,8 @@ var init_config = __esm(() => {
311
317
  PLAYWRIGHT_PORTS_DIR = resolve(RUN_DIR, "playwright-ports");
312
318
  mkdirSync(STATE_DIR, { recursive: true });
313
319
  mkdirSync(DATA_DIR, { recursive: true });
320
+ mkdirSync(UPLOADS_DIR, { recursive: true });
321
+ mkdirSync(VAULT_DIR, { recursive: true, mode: 448 });
314
322
  mkdirSync(LOG_DIR, { recursive: true });
315
323
  mkdirSync(PROGRESS_DIR, { recursive: true });
316
324
  mkdirSync(DM_CMD_DIR, { recursive: true });
@@ -321,10 +329,14 @@ var init_config = __esm(() => {
321
329
  mkdirSync(WORKSPACE_DIR, { recursive: true });
322
330
  mkdirSync(TOPIC_WORKSPACE_DIR, { recursive: true });
323
331
  mkdirSync(SHARED_WIKI_DIR, { recursive: true });
332
+ mkdirSync(CRON_WORKSPACE_DIR, { recursive: true });
324
333
  mkdirSync(CONTEXTS_DIR, { recursive: true });
325
- mkdirSync(BROWSER_PROFILES_DIR, { recursive: true });
326
334
  mkdirSync(DM_WORKSPACE_DIR, { recursive: true });
327
335
  mkdirSync(SESSION_WORKSPACE_DIR, { recursive: true });
336
+ mkdirSync(BROWSER_DIR, { recursive: true });
337
+ mkdirSync(BROWSER_PROFILES_DIR, { recursive: true });
338
+ mkdirSync(BINARIES_DIR, { recursive: true });
339
+ mkdirSync(SECRETS_DIR, { recursive: true, mode: 448 });
328
340
  ACTIVE_QUERY_STALE_MS = 10 * 60 * 1000;
329
341
  AGENTS_PROMPTS_DIR = resolve(PROJECT_ROOT, "src/prompts/agents");
330
342
  RESOURCES_DIR = resolve(PROJECT_ROOT, "src/resources");
@@ -481,8 +493,9 @@ function initializeVaultDatabase(database) {
481
493
  }
482
494
  }
483
495
  function openVaultDatabase(dataDir) {
484
- const path = join2(dataDir, "vault.db");
485
- mkdirSync2(dataDir, { recursive: true });
496
+ const vaultDir = join2(dataDir, "vault");
497
+ const path = join2(vaultDir, "vault.db");
498
+ mkdirSync2(vaultDir, { recursive: true, mode: 448 });
486
499
  const database = new Database(path, { create: true });
487
500
  chmodSync2(path, 384);
488
501
  initializeVaultDatabase(database);
@@ -1119,7 +1132,12 @@ var init_shared = __esm(() => {
1119
1132
  init_config();
1120
1133
  init_logger();
1121
1134
  __dirname2 = dirname2(fileURLToPath2(import.meta.url));
1122
- trustedWorkspaceRoots = [WORKSPACE_DIR, DM_WORKSPACE_DIR, TOPIC_WORKSPACE_DIR].map((root) => resolve2(root));
1135
+ trustedWorkspaceRoots = [
1136
+ WORKSPACE_DIR,
1137
+ DM_WORKSPACE_DIR,
1138
+ SESSION_WORKSPACE_DIR,
1139
+ TOPIC_WORKSPACE_DIR
1140
+ ].map((root) => resolve2(root));
1123
1141
  FIXTURES_DIR = join4(__dirname2, "..", "fixtures");
1124
1142
  UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
1125
1143
  });
@@ -2029,7 +2047,7 @@ function defaultWorkspaceDir() {
2029
2047
  return envPath("NEGOTIUM_WORKSPACE_DIR", join8(defaultStateDir(), "workspace"));
2030
2048
  }
2031
2049
  function defaultSessionAsksDir() {
2032
- const runDir = envPath("NEGOTIUM_RUN_DIR", join8(defaultStateDir(), "run"));
2050
+ const runDir = envPath("NEGOTIUM_RUN_DIR", join8(defaultStateDir(), "runtime"));
2033
2051
  return join8(runDir, "session-asks");
2034
2052
  }
2035
2053
  function defaultSessionsDatabasePath() {
@@ -2210,15 +2228,8 @@ import {
2210
2228
  writeFileSync as writeFileSync4
2211
2229
  } from "fs";
2212
2230
  import { dirname as dirname6, join as join9 } from "path";
2213
- function safeUserIdComponent(userId) {
2214
- const str = String(userId);
2215
- if (!str || /[/\\]|\.\./.test(str)) {
2216
- throw new Error(`conversations: refusing unsafe userId path component: ${str}`);
2217
- }
2218
- return str;
2219
- }
2220
- function conversationDir(userId) {
2221
- return join9(resolveStorageDataDir(), "conversations", safeUserIdComponent(userId));
2231
+ function conversationDir(_userId) {
2232
+ return join9(resolveStorageDataDir(), "conversations");
2222
2233
  }
2223
2234
  function topicFilename(topicName) {
2224
2235
  const t = sanitizeTopicName(topicName, true);
@@ -2500,7 +2511,7 @@ function writeRollout(options) {
2500
2511
  version: 1,
2501
2512
  cwd: options.cwd,
2502
2513
  createdAt: existingCreatedAt(path) ?? new Date().toISOString(),
2503
- sdkVersion: "0.1.53"
2514
+ sdkVersion: "0.1.54"
2504
2515
  }
2505
2516
  },
2506
2517
  ...messages
@@ -2677,13 +2688,6 @@ __export(exports_tasks, {
2677
2688
  });
2678
2689
  import { existsSync as existsSync10, mkdirSync as mkdirSync9, readFileSync as readFileSync7, renameSync as renameSync4, statSync as statSync3, writeFileSync as writeFileSync6 } from "fs";
2679
2690
  import { dirname as dirname7, join as join12 } from "path";
2680
- function safeUserIdComponent2(userId) {
2681
- const str = String(userId);
2682
- if (!str || /[/\\]|\.\./.test(str)) {
2683
- throw new Error(`tasks: refusing unsafe userId path component: ${str}`);
2684
- }
2685
- return str;
2686
- }
2687
2691
  function safeTaskScopeKey(scopeKey) {
2688
2692
  const safe = sanitizeFileName(scopeKey);
2689
2693
  if (!safe || safe === "." || safe === "..") {
@@ -2695,7 +2699,7 @@ function taskScopeKey(opts) {
2695
2699
  return opts.topicId?.trim() || opts.session || "default";
2696
2700
  }
2697
2701
  function getTaskFilePath(userId, scopeKey) {
2698
- return join12(resolveStorageDataDir(), "tasks", safeUserIdComponent2(userId), `${safeTaskScopeKey(scopeKey)}.json`);
2702
+ return join12(resolveStorageDataDir(), "tasks", `${safeTaskScopeKey(scopeKey)}.json`);
2699
2703
  }
2700
2704
  function readTasks(userId, scopeKey) {
2701
2705
  const path = getTaskFilePath(userId, scopeKey);
@@ -3680,7 +3684,7 @@ function getMcpServersForQuery(opts) {
3680
3684
  if (!opts.topicId)
3681
3685
  throw new Error("getMcpServersForQuery: cron sessionType requires topicId");
3682
3686
  return getCronMcpServers({
3683
- userId: opts.userId || "default",
3687
+ userId: opts.userId || "local",
3684
3688
  session: opts.session || "cron",
3685
3689
  topicId: opts.topicId,
3686
3690
  queryId: opts.queryId,
@@ -3697,7 +3701,7 @@ function getMcpServersForQuery(opts) {
3697
3701
  }
3698
3702
  if (opts.sessionType === "dm" || opts.sessionType === "ephemeral") {
3699
3703
  return getDmMcpServers({
3700
- userId: opts.userId || "default",
3704
+ userId: opts.userId || "local",
3701
3705
  agent: opts.agent,
3702
3706
  playwrightPort: opts.playwrightPort,
3703
3707
  playwrightCapability: opts.playwrightCapability
@@ -3705,7 +3709,7 @@ function getMcpServersForQuery(opts) {
3705
3709
  }
3706
3710
  if (opts.sessionType === "manager") {
3707
3711
  return getManagerMcpServers({
3708
- userId: opts.userId || "default",
3712
+ userId: opts.userId || "local",
3709
3713
  session: opts.session,
3710
3714
  topicId: opts.topicId,
3711
3715
  queryId: opts.queryId,
@@ -3722,7 +3726,7 @@ function getMcpServersForQuery(opts) {
3722
3726
  });
3723
3727
  }
3724
3728
  return getForumMcpServers({
3725
- userId: opts.userId || "default",
3729
+ userId: opts.userId || "local",
3726
3730
  session: opts.session || "default",
3727
3731
  topicId: opts.topicId,
3728
3732
  subagentParentTopicId: opts.subagentParentTopicId,
@@ -4580,7 +4584,7 @@ var exports_version = {};
4580
4584
  __export(exports_version, {
4581
4585
  NEGOTIUM_VERSION: () => NEGOTIUM_VERSION
4582
4586
  });
4583
- var NEGOTIUM_VERSION = "0.1.49";
4587
+ var NEGOTIUM_VERSION = "0.2.0";
4584
4588
 
4585
4589
  // ../../packages/core/src/agents/codex-native-multi-agent.ts
4586
4590
  import { spawn as spawn3 } from "child_process";
@@ -9730,7 +9734,7 @@ function createBrowserProfile(ownerId, rawName) {
9730
9734
  const name = normalizeBrowserProfileName(rawName);
9731
9735
  if (name === DEFAULT_BROWSER_PROFILE)
9732
9736
  return name;
9733
- db.query("INSERT OR IGNORE INTO browser_profiles (owner_id, name, created_at) VALUES (?, ?, ?)").run(ownerId, name, new Date().toISOString());
9737
+ db.query("INSERT OR IGNORE INTO browser_profiles (owner_id, name, created_at) VALUES (?, ?, ?)").run("local", name, new Date().toISOString());
9734
9738
  return name;
9735
9739
  }
9736
9740
  function assignTopicBrowserProfile(opts) {
@@ -9750,10 +9754,9 @@ function assignTopicBrowserProfile(opts) {
9750
9754
  return { previous, profile };
9751
9755
  }
9752
9756
  function listBrowserProfiles(ownerId) {
9753
- const records = db.query("SELECT name, created_at FROM browser_profiles WHERE owner_id = ? ORDER BY name").all(ownerId);
9757
+ const records = db.query("SELECT name, created_at FROM browser_profiles WHERE owner_id = ? ORDER BY name").all("local");
9754
9758
  const topics = db.query(`SELECT t.id, t.title, t.browser_profile FROM api_topics t
9755
- WHERE t.browser_profile_owner = ?
9756
- ORDER BY t.title`).all(ownerId);
9759
+ ORDER BY t.title`).all();
9757
9760
  const byName = new Map;
9758
9761
  byName.set(DEFAULT_BROWSER_PROFILE, {
9759
9762
  name: DEFAULT_BROWSER_PROFILE,
@@ -9798,7 +9801,6 @@ import {
9798
9801
  existsSync as existsSync15,
9799
9802
  mkdirSync as mkdirSync10,
9800
9803
  readFileSync as readFileSync12,
9801
- renameSync as renameSync6,
9802
9804
  rmSync as rmSync2,
9803
9805
  unlinkSync as unlinkSync11,
9804
9806
  writeFileSync as writeFileSync8
@@ -9814,7 +9816,7 @@ function defaultTopicBinding(userId, topic) {
9814
9816
  if (!topic)
9815
9817
  return managerHost.resolveNamedBinding(userId, "default");
9816
9818
  const ownerId = getBrowserProfileOwner(topic, userId);
9817
- const profile = migrateLegacyTopicProfile(ownerId, topic);
9819
+ const profile = getTopicBrowserProfile(topic);
9818
9820
  return managerHost.resolveNamedBinding(ownerId, profile);
9819
9821
  }
9820
9822
  function resolvePlaywrightProfileBinding(ownerId, rawProfile) {
@@ -9826,34 +9828,15 @@ function validateProfileBinding(binding) {
9826
9828
  }
9827
9829
  return Object.freeze({ ...binding });
9828
9830
  }
9829
- function defaultNamedBinding(ownerId, rawProfile) {
9831
+ function defaultNamedBinding(_ownerId, rawProfile) {
9830
9832
  const profile = normalizeBrowserProfileName(rawProfile);
9831
- const instanceKey = `profile:${encodeURIComponent(ownerId)}:${profile}`;
9833
+ const instanceKey = `profile:${profile}`;
9832
9834
  return {
9833
9835
  instanceKey,
9834
- ownerId,
9836
+ ownerId: "local",
9835
9837
  profile
9836
9838
  };
9837
9839
  }
9838
- function legacyBrowserProfileName(topic) {
9839
- return `legacy_${createHash3("sha256").update(topic).digest("hex").slice(0, 12)}`;
9840
- }
9841
- function migrateLegacyTopicProfile(ownerId, topic) {
9842
- const current2 = getTopicBrowserProfile(topic);
9843
- if (current2 !== "default" || !hasBrowserProfileTopic(topic))
9844
- return current2;
9845
- const legacyDir = resolve12(BROWSER_PROFILES_DIR, sanitizeTopicName(topic));
9846
- if (!existsSync15(legacyDir))
9847
- return current2;
9848
- const profile = legacyBrowserProfileName(topic);
9849
- const profileDir = defaultProfileDir(ownerId, profile);
9850
- mkdirSync10(dirname10(profileDir), { recursive: true });
9851
- if (!existsSync15(profileDir))
9852
- renameSync6(legacyDir, profileDir);
9853
- assignTopicBrowserProfile({ topicId: topic, actorUserId: ownerId, profile });
9854
- logger.info({ ownerId, topic, profile }, "Adopted legacy topic browser profile");
9855
- return profile;
9856
- }
9857
9840
  function defaultChildEnvironment(context) {
9858
9841
  const childEnvironment = {};
9859
9842
  for (const key of [
@@ -9884,6 +9867,10 @@ function defaultChildEnvironment(context) {
9884
9867
  return childEnvironment;
9885
9868
  }
9886
9869
  function parseInstanceKey(instanceKey) {
9870
+ const shared = /^profile:(.+)$/.exec(instanceKey);
9871
+ if (shared && !shared[1].includes(":")) {
9872
+ return { ownerId: "local", profile: normalizeBrowserProfileName(shared[1]) };
9873
+ }
9887
9874
  const match = /^profile:([^:]+):(.+)$/.exec(instanceKey);
9888
9875
  if (!match)
9889
9876
  return { ownerId: "legacy", profile: sanitizeTopicName(instanceKey) };
@@ -10026,12 +10013,8 @@ async function allocatePort() {
10026
10013
  function releasePort(port) {
10027
10014
  usedPorts.delete(port);
10028
10015
  }
10029
- function ownerDirectory(ownerId) {
10030
- const digest = createHash3("sha256").update(ownerId).digest("hex").slice(0, 16);
10031
- return `${sanitizeTopicName(ownerId).slice(0, 24)}_${digest}`;
10032
- }
10033
- function defaultProfileDir(ownerId, profile) {
10034
- return resolve12(BROWSER_PROFILES_DIR, "profiles", ownerDirectory(ownerId), profile);
10016
+ function defaultProfileDir(_ownerId, profile) {
10017
+ return resolve12(BROWSER_PROFILES_DIR, profile);
10035
10018
  }
10036
10019
  function resolveUserDataDir(instanceKey) {
10037
10020
  return managerHost.resolveInstanceDataDir(instanceKey);
@@ -12440,7 +12423,7 @@ var init_idle_archiver = __esm(async () => {
12440
12423
  });
12441
12424
 
12442
12425
  // ../../packages/core/src/agents/topic-cleanup.ts
12443
- import { mkdirSync as mkdirSync12, renameSync as renameSync7, unlinkSync as unlinkSync12, writeFileSync as writeFileSync10 } from "fs";
12426
+ import { mkdirSync as mkdirSync12, renameSync as renameSync6, unlinkSync as unlinkSync12, writeFileSync as writeFileSync10 } from "fs";
12444
12427
  import { dirname as dirname12 } from "path";
12445
12428
  function collectSessionIdsByAgent(entries, extraSessions = []) {
12446
12429
  const out = new Map;
@@ -12511,7 +12494,7 @@ function createTopicLogMaintenance(host) {
12511
12494
  writeFileSync10(tempPath, retained.length > 0 ? `${retained.map((entry) => JSON.stringify(entry)).join(`
12512
12495
  `)}
12513
12496
  ` : "", { flag: "wx" });
12514
- renameSync7(tempPath, path);
12497
+ renameSync6(tempPath, path);
12515
12498
  } catch (e) {
12516
12499
  try {
12517
12500
  unlinkSync12(tempPath);
@@ -14767,7 +14750,7 @@ var init_session_inbox_cleanup = __esm(() => {
14767
14750
  });
14768
14751
 
14769
14752
  // ../../packages/core/src/query/state.ts
14770
- import { mkdirSync as mkdirSync14, renameSync as renameSync8, unlinkSync as unlinkSync15, writeFileSync as writeFileSync12 } from "fs";
14753
+ import { mkdirSync as mkdirSync14, renameSync as renameSync7, unlinkSync as unlinkSync15, writeFileSync as writeFileSync12 } from "fs";
14771
14754
  import { basename as basename4, join as join24 } from "path";
14772
14755
  function createQueryStateStore(options) {
14773
14756
  const sanitize = options.sanitizeTopicId ?? sanitizeId;
@@ -14789,7 +14772,7 @@ function createQueryStateStore(options) {
14789
14772
  const target = queryStateFile(userId, topicId);
14790
14773
  const tmp = `${target}.${process.pid}.${Date.now()}.tmp`;
14791
14774
  writeFileSync12(tmp, JSON.stringify(state));
14792
- renameSync8(tmp, target);
14775
+ renameSync7(tmp, target);
14793
14776
  },
14794
14777
  clear(userId, topicId, legacyTopicName) {
14795
14778
  const paths = [
@@ -15943,7 +15926,7 @@ function promptWithAttachments(prompt, attachments) {
15943
15926
  return composeAttachmentPrompt(prompt, attachments.map(({ filename, path }) => attachmentPromptLine(filename, path)));
15944
15927
  }
15945
15928
  function ingestAttachment(args) {
15946
- const destDir = join26(workspaceCwdFor(args.topicId), "uploads");
15929
+ const destDir = join26(UPLOADS_DIR, args.topicId);
15947
15930
  mkdirSync16(destDir, { recursive: true });
15948
15931
  const safeName = safeAttachmentFilename(args.filename, "upload");
15949
15932
  const destPath = join26(destDir, `${Date.now()}-${randomUUID15().slice(0, 8)}-${safeName}`);
@@ -20616,7 +20599,7 @@ function renderTurnFooter(msg) {
20616
20599
 
20617
20600
  // ../../packages/core/src/outbox/file-ops.ts
20618
20601
  import { AsyncLocalStorage as AsyncLocalStorage2 } from "async_hooks";
20619
- import { appendFileSync as appendFileSync2, existsSync as existsSync21, readFileSync as readFileSync18, renameSync as renameSync9, unlinkSync as unlinkSync17 } from "fs";
20602
+ import { appendFileSync as appendFileSync2, existsSync as existsSync21, readFileSync as readFileSync18, renameSync as renameSync8, unlinkSync as unlinkSync17 } from "fs";
20620
20603
  function host() {
20621
20604
  return hostContext.getStore() ?? defaultHost2;
20622
20605
  }
@@ -20657,7 +20640,7 @@ function drainOutboxFile(filePath, label) {
20657
20640
  if (!existsSync21(filePath))
20658
20641
  return null;
20659
20642
  try {
20660
- renameSync9(filePath, processingPath);
20643
+ renameSync8(filePath, processingPath);
20661
20644
  } catch (e) {
20662
20645
  host().logger.warn({ err: e, filePath }, `${label}: failed to rename for processing`);
20663
20646
  return null;
@@ -23678,7 +23661,7 @@ import {
23678
23661
  openSync as openSync3,
23679
23662
  readdirSync as readdirSync9,
23680
23663
  readFileSync as readFileSync20,
23681
- renameSync as renameSync10,
23664
+ renameSync as renameSync9,
23682
23665
  statSync as statSync12,
23683
23666
  unlinkSync as unlinkSync18,
23684
23667
  writeFileSync as writeFileSync17
@@ -23798,7 +23781,7 @@ function atomicWriteFile(path, content) {
23798
23781
  const temporaryPath = `${path}.tmp.${process.pid}.${Date.now()}`;
23799
23782
  writeFileSync17(temporaryPath, content, "utf-8");
23800
23783
  try {
23801
- renameSync10(temporaryPath, path);
23784
+ renameSync9(temporaryPath, path);
23802
23785
  } catch (error2) {
23803
23786
  try {
23804
23787
  unlinkSync18(temporaryPath);
@@ -24531,7 +24514,7 @@ import {
24531
24514
  existsSync as existsSync25,
24532
24515
  mkdirSync as mkdirSync25,
24533
24516
  readdirSync as readdirSync10,
24534
- renameSync as renameSync11,
24517
+ renameSync as renameSync10,
24535
24518
  statSync as statSync13,
24536
24519
  unlinkSync as unlinkSync19,
24537
24520
  writeFileSync as writeFileSync18
@@ -24555,7 +24538,7 @@ function writeLog(entry) {
24555
24538
  const stat = statSync13(file);
24556
24539
  if (stat.size >= MAX_FILE_SIZE) {
24557
24540
  const rotated = file.replace(/\.jsonl$/, `.${Date.now()}.jsonl`);
24558
- renameSync11(file, rotated);
24541
+ renameSync10(file, rotated);
24559
24542
  }
24560
24543
  } catch {}
24561
24544
  try {
@@ -26143,7 +26126,7 @@ var init_spec = __esm(() => {
26143
26126
  });
26144
26127
 
26145
26128
  // ../../packages/mcp-host/src/manifest.ts
26146
- import { existsSync as existsSync26, mkdirSync as mkdirSync26, readFileSync as readFileSync21, renameSync as renameSync12, writeFileSync as writeFileSync19 } from "fs";
26129
+ import { existsSync as existsSync26, mkdirSync as mkdirSync26, readFileSync as readFileSync21, renameSync as renameSync11, writeFileSync as writeFileSync19 } from "fs";
26147
26130
  import { dirname as dirname17 } from "path";
26148
26131
  import { z as z13 } from "zod";
26149
26132
 
@@ -26212,7 +26195,7 @@ class McpManifest {
26212
26195
  const tmp = `${this.file}.tmp-${process.pid}`;
26213
26196
  writeFileSync19(tmp, `${JSON.stringify(payload, null, 2)}
26214
26197
  `);
26215
- renameSync12(tmp, this.file);
26198
+ renameSync11(tmp, this.file);
26216
26199
  }
26217
26200
  }
26218
26201
  var manifestEntrySchema, manifestFileSchema;
@@ -26243,7 +26226,7 @@ import {
26243
26226
  mkdirSync as mkdirSync27,
26244
26227
  readdirSync as readdirSync11,
26245
26228
  readFileSync as readFileSync22,
26246
- renameSync as renameSync13,
26229
+ renameSync as renameSync12,
26247
26230
  unlinkSync as unlinkSync20,
26248
26231
  writeFileSync as writeFileSync20
26249
26232
  } from "fs";
@@ -26563,7 +26546,7 @@ class McpHost {
26563
26546
  const file = join37(this.portsDir, fileName);
26564
26547
  const tmp = `${file}.tmp-${process.pid}`;
26565
26548
  writeFileSync20(tmp, String(port));
26566
- renameSync13(tmp, file);
26549
+ renameSync12(tmp, file);
26567
26550
  } catch (e) {
26568
26551
  this.log("warn", "Failed to write MCP port file", { fileName, port, err: String(e) });
26569
26552
  }
@@ -26961,7 +26944,7 @@ import {
26961
26944
  existsSync as existsSync28,
26962
26945
  mkdirSync as mkdirSync29,
26963
26946
  readFileSync as readFileSync24,
26964
- renameSync as renameSync14,
26947
+ renameSync as renameSync13,
26965
26948
  unlinkSync as unlinkSync21,
26966
26949
  writeFileSync as writeFileSync22
26967
26950
  } from "fs";
@@ -27456,7 +27439,7 @@ function writeNodeDaemonInfo(port, startedAt) {
27456
27439
  writeFileSync22(temporary, `${JSON.stringify(info, null, 2)}
27457
27440
  `, { mode: 384 });
27458
27441
  chmodSync5(temporary, 384);
27459
- renameSync14(temporary, NODE_DAEMON_INFO_PATH);
27442
+ renameSync13(temporary, NODE_DAEMON_INFO_PATH);
27460
27443
  chmodSync5(NODE_DAEMON_INFO_PATH, 384);
27461
27444
  return info;
27462
27445
  }
@@ -38926,7 +38909,7 @@ import {
38926
38909
  mkdirSync as mkdirSync32,
38927
38910
  openSync as openSync4,
38928
38911
  readFileSync as readFileSync25,
38929
- renameSync as renameSync15,
38912
+ renameSync as renameSync14,
38930
38913
  rmSync as rmSync9,
38931
38914
  statSync as statSync17,
38932
38915
  unlinkSync as unlinkSync22,
@@ -39047,7 +39030,7 @@ function withJoinCredentialLock(operation) {
39047
39030
  }
39048
39031
  const stalePath = `${lockPath}.stale.${process.pid}.${randomUUID28()}`;
39049
39032
  try {
39050
- renameSync15(lockPath, stalePath);
39033
+ renameSync14(lockPath, stalePath);
39051
39034
  rmSync9(stalePath, { recursive: true, force: true });
39052
39035
  } catch (staleError) {
39053
39036
  if (staleError.code !== "ENOENT")
@@ -39145,7 +39128,7 @@ function saveJoinWhileLocked(join41, options = {}) {
39145
39128
  closeSync4(fd);
39146
39129
  fd = undefined;
39147
39130
  if (options.replaceExisting) {
39148
- renameSync15(temporaryPath, path);
39131
+ renameSync14(temporaryPath, path);
39149
39132
  } else {
39150
39133
  linkSync(temporaryPath, path);
39151
39134
  unlinkSync22(temporaryPath);
@@ -41931,7 +41914,7 @@ import {
41931
41914
  mkdirSync as mkdirSync34,
41932
41915
  openSync as openSync5,
41933
41916
  readFileSync as readFileSync26,
41934
- renameSync as renameSync16,
41917
+ renameSync as renameSync15,
41935
41918
  unlinkSync as unlinkSync23,
41936
41919
  writeFileSync as writeFileSync24
41937
41920
  } from "fs";
@@ -42030,7 +42013,7 @@ function replacePendingEnrollment(pending2) {
42030
42013
  fsyncSync3(fd);
42031
42014
  closeSync5(fd);
42032
42015
  fd = undefined;
42033
- renameSync16(temporaryPath, path);
42016
+ renameSync15(temporaryPath, path);
42034
42017
  const directoryFd = openSync5(directory, "r");
42035
42018
  try {
42036
42019
  fsyncSync3(directoryFd);
@@ -43657,6 +43640,476 @@ var init_init = __esm(async () => {
43657
43640
  await init_src();
43658
43641
  });
43659
43642
 
43643
+ // ../../packages/core/src/migration/single-user.ts
43644
+ import { createHash as createHash11, randomUUID as randomUUID34 } from "crypto";
43645
+ import {
43646
+ chmodSync as chmodSync8,
43647
+ copyFileSync as copyFileSync6,
43648
+ existsSync as existsSync36,
43649
+ mkdirSync as mkdirSync35,
43650
+ readdirSync as readdirSync14,
43651
+ readFileSync as readFileSync27,
43652
+ renameSync as renameSync16,
43653
+ rmSync as rmSync11,
43654
+ statSync as statSync19,
43655
+ writeFileSync as writeFileSync25
43656
+ } from "fs";
43657
+ import { homedir as homedir12 } from "os";
43658
+ import { basename as basename14, dirname as dirname23, join as join42, resolve as resolve25 } from "path";
43659
+ function entries(path) {
43660
+ return existsSync36(path) && statSync19(path).isDirectory() ? readdirSync14(path) : [];
43661
+ }
43662
+ function processIsAlive2(pid) {
43663
+ if (!Number.isInteger(pid) || pid <= 0)
43664
+ return false;
43665
+ try {
43666
+ process.kill(pid, 0);
43667
+ return true;
43668
+ } catch (error2) {
43669
+ return error2.code === "EPERM";
43670
+ }
43671
+ }
43672
+ function assertNoActiveNode(stateDir2) {
43673
+ for (const runtimeName of ["run", "runtime"]) {
43674
+ const infoPath = join42(stateDir2, runtimeName, "node-daemon.json");
43675
+ if (!existsSync36(infoPath))
43676
+ continue;
43677
+ try {
43678
+ const value = JSON.parse(readFileSync27(infoPath, "utf8"));
43679
+ if (typeof value.pid === "number" && processIsAlive2(value.pid)) {
43680
+ throw new Error(`Refusing migration while Negotium pid ${value.pid} is active.`);
43681
+ }
43682
+ } catch (error2) {
43683
+ if (error2 instanceof SyntaxError) {
43684
+ throw new Error(`Refusing migration: unreadable process metadata at ${infoPath}.`);
43685
+ }
43686
+ throw error2;
43687
+ }
43688
+ }
43689
+ const dbPath = join42(stateDir2, "data", "sessions.db");
43690
+ if (!existsSync36(dbPath))
43691
+ return;
43692
+ const database = new Database(dbPath, { readonly: true });
43693
+ try {
43694
+ const hasLeases = database.query("SELECT 1 FROM sqlite_master WHERE type='table' AND name='runtime_process_leases'").get();
43695
+ if (!hasLeases)
43696
+ return;
43697
+ const leases = database.query("SELECT role, pid FROM runtime_process_leases").all();
43698
+ const active2 = leases.find((lease) => processIsAlive2(lease.pid));
43699
+ if (active2)
43700
+ throw new Error(`Refusing migration while ${active2.role} pid ${active2.pid} is active.`);
43701
+ } finally {
43702
+ database.close();
43703
+ }
43704
+ }
43705
+ function oldOwnerDirectory(ownerId) {
43706
+ const digest = createHash11("sha256").update(ownerId).digest("hex").slice(0, 16);
43707
+ return `${sanitizeTopicName(ownerId).slice(0, 24)}_${digest}`;
43708
+ }
43709
+ function addDirectoryContents(operations, sourceDir, destinationDir, stagingDir) {
43710
+ for (const name of entries(sourceDir)) {
43711
+ operations.push({
43712
+ source: join42(sourceDir, name),
43713
+ staged: join42(stagingDir, randomUUID34()),
43714
+ ...destinationDir ? { destination: join42(destinationDir, name) } : {},
43715
+ state: "pending"
43716
+ });
43717
+ }
43718
+ }
43719
+ function assertNoCollisions(operations) {
43720
+ const destinations = new Set;
43721
+ for (const operation of operations) {
43722
+ if (!operation.destination)
43723
+ continue;
43724
+ if (existsSync36(operation.destination)) {
43725
+ throw new Error(`Migration collision at ${operation.destination}.`);
43726
+ }
43727
+ if (destinations.has(operation.destination)) {
43728
+ throw new Error(`Multiple legacy paths target ${operation.destination}.`);
43729
+ }
43730
+ destinations.add(operation.destination);
43731
+ }
43732
+ }
43733
+ function writeJournal(path, journal) {
43734
+ writeFileSync25(path, `${JSON.stringify(journal, null, 2)}
43735
+ `, { mode: 384 });
43736
+ }
43737
+ function rollbackFilesystem(journal, journalPath) {
43738
+ for (const operation of [...journal.operations].reverse()) {
43739
+ const current3 = operation.state === "placed" ? operation.destination : operation.staged;
43740
+ if (!current3 || !existsSync36(current3) || existsSync36(operation.source))
43741
+ continue;
43742
+ mkdirSync35(dirname23(operation.source), { recursive: true });
43743
+ renameSync16(current3, operation.source);
43744
+ operation.state = "pending";
43745
+ }
43746
+ journal.phase = "filesystem";
43747
+ writeJournal(journalPath, journal);
43748
+ }
43749
+ function tablesWithColumn(database, columnName) {
43750
+ const tables = database.query("SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%'").all();
43751
+ return tables.map((row) => row.name).filter((table) => database.query(`PRAGMA table_info("${table.replaceAll('"', '""')}")`).all().some((column) => column.name === columnName));
43752
+ }
43753
+ function selectedTopicIds(dbPath, source) {
43754
+ if (!existsSync36(dbPath))
43755
+ return null;
43756
+ const database = new Database(dbPath, { readonly: true });
43757
+ try {
43758
+ const hasMemberships = database.query("SELECT 1 FROM sqlite_master WHERE type='table' AND name='topic_members'").get();
43759
+ if (!hasMemberships)
43760
+ return null;
43761
+ return new Set(database.query("SELECT topic_id FROM topic_members WHERE user_id = ?").all(source).map((row) => row.topic_id));
43762
+ } finally {
43763
+ database.close();
43764
+ }
43765
+ }
43766
+ function migrateDatabase(dbPath, source, masterKey, legacyTopicProfiles = []) {
43767
+ if (!existsSync36(dbPath))
43768
+ return [];
43769
+ const database = new Database(dbPath);
43770
+ const userTables = tablesWithColumn(database, "user_id");
43771
+ const ownerUserTables = tablesWithColumn(database, "owner_user_id");
43772
+ const migratedTables = [...new Set([...userTables, ...ownerUserTables])];
43773
+ const retainedTopicIds = selectedTopicIds(dbPath, source);
43774
+ const quote = (name) => `"${name.replaceAll('"', '""')}"`;
43775
+ try {
43776
+ if (source !== CANONICAL_LOCAL_USER_ID) {
43777
+ for (const [column, tables] of [
43778
+ ["user_id", userTables],
43779
+ ["owner_user_id", ownerUserTables]
43780
+ ]) {
43781
+ for (const table of tables) {
43782
+ const collision = database.query(`SELECT COUNT(*) AS count FROM ${quote(table)} WHERE ${column} = ?`).get(CANONICAL_LOCAL_USER_ID)?.count;
43783
+ if (collision)
43784
+ throw new Error(`Database collision: ${table}.${column} already contains local rows.`);
43785
+ }
43786
+ }
43787
+ const hasUsers = database.query("SELECT 1 FROM sqlite_master WHERE type='table' AND name='users'").get();
43788
+ if (hasUsers) {
43789
+ const localUser = database.query("SELECT 1 FROM users WHERE id = ?").get(CANONICAL_LOCAL_USER_ID);
43790
+ if (localUser)
43791
+ throw new Error("Database collision: users already contains canonical local.");
43792
+ }
43793
+ }
43794
+ database.exec("PRAGMA foreign_keys = OFF");
43795
+ database.exec("BEGIN IMMEDIATE");
43796
+ for (const table of userTables) {
43797
+ database.query(`DELETE FROM ${quote(table)} WHERE user_id <> ?`).run(source);
43798
+ if (table === "vault" && source !== CANONICAL_LOCAL_USER_ID) {
43799
+ if (!masterKey)
43800
+ throw new Error("Cannot migrate encrypted vault rows without vault-master-key.");
43801
+ const rows = database.query("SELECT key, value FROM vault WHERE user_id = ?").all(source);
43802
+ for (const row of rows) {
43803
+ const plaintext = decryptVaultValue(source, row.key, row.value, masterKey).value;
43804
+ database.query("UPDATE vault SET value = ? WHERE user_id = ? AND key = ?").run(encryptVaultValue(CANONICAL_LOCAL_USER_ID, row.key, plaintext, masterKey), source, row.key);
43805
+ }
43806
+ }
43807
+ database.query(`UPDATE ${quote(table)} SET user_id = ? WHERE user_id = ?`).run(CANONICAL_LOCAL_USER_ID, source);
43808
+ }
43809
+ for (const table of ownerUserTables) {
43810
+ database.query(`DELETE FROM ${quote(table)} WHERE owner_user_id <> ?`).run(source);
43811
+ database.query(`UPDATE ${quote(table)} SET owner_user_id = ? WHERE owner_user_id = ?`).run(CANONICAL_LOCAL_USER_ID, source);
43812
+ }
43813
+ if (retainedTopicIds) {
43814
+ database.exec("CREATE TEMP TABLE migration_selected_topics (id TEXT PRIMARY KEY)");
43815
+ for (const topicId of retainedTopicIds) {
43816
+ database.query("INSERT INTO migration_selected_topics (id) VALUES (?)").run(topicId);
43817
+ }
43818
+ const topicTables = database.query("SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%'").all().map((row) => row.name).filter((table) => database.query(`PRAGMA table_info(${quote(table)})`).all().some((column) => column.name === "topic_id"));
43819
+ for (const table of topicTables) {
43820
+ database.query(`DELETE FROM ${quote(table)} WHERE topic_id NOT IN (SELECT id FROM migration_selected_topics)`).run();
43821
+ }
43822
+ if (database.query("SELECT 1 FROM sqlite_master WHERE type='table' AND name='api_topics'").get()) {
43823
+ database.query("DELETE FROM api_topics WHERE id NOT IN (SELECT id FROM migration_selected_topics)").run();
43824
+ }
43825
+ }
43826
+ if (database.query("SELECT 1 FROM sqlite_master WHERE type='table' AND name='browser_profiles'").get()) {
43827
+ database.query("DELETE FROM browser_profiles WHERE owner_id <> ?").run(source);
43828
+ database.query("UPDATE browser_profiles SET owner_id = ? WHERE owner_id = ?").run(CANONICAL_LOCAL_USER_ID, source);
43829
+ }
43830
+ if (database.query("SELECT 1 FROM sqlite_master WHERE type='table' AND name='api_topics'").get() && database.query("PRAGMA table_info(api_topics)").all().some((column) => column.name === "browser_profile_owner")) {
43831
+ database.query("UPDATE api_topics SET browser_profile_owner = ? WHERE browser_profile_owner = ?").run(CANONICAL_LOCAL_USER_ID, source);
43832
+ if (retainedTopicIds) {
43833
+ database.query("UPDATE api_topics SET browser_profile_owner = ? WHERE browser_profile_owner IS NOT NULL").run(CANONICAL_LOCAL_USER_ID);
43834
+ } else {
43835
+ database.query("DELETE FROM api_topics WHERE browser_profile_owner IS NOT NULL AND browser_profile_owner <> ?").run(CANONICAL_LOCAL_USER_ID);
43836
+ }
43837
+ for (const binding of legacyTopicProfiles) {
43838
+ database.query("UPDATE api_topics SET browser_profile = ?, browser_profile_owner = ? WHERE id = ?").run(binding.profile, CANONICAL_LOCAL_USER_ID, binding.topicId);
43839
+ }
43840
+ }
43841
+ if (database.query("SELECT 1 FROM sqlite_master WHERE type='table' AND name='users'").get()) {
43842
+ database.query("DELETE FROM users WHERE id <> ?").run(source);
43843
+ database.query("UPDATE users SET id = ? WHERE id = ?").run(CANONICAL_LOCAL_USER_ID, source);
43844
+ }
43845
+ database.exec("COMMIT");
43846
+ database.exec("PRAGMA foreign_keys = ON");
43847
+ const fkErrors = database.query("PRAGMA foreign_key_check").all();
43848
+ if (fkErrors.length > 0)
43849
+ throw new Error("Database migration left foreign-key violations.");
43850
+ return migratedTables;
43851
+ } catch (error2) {
43852
+ try {
43853
+ database.exec("ROLLBACK");
43854
+ } catch {}
43855
+ throw error2;
43856
+ } finally {
43857
+ database.close();
43858
+ }
43859
+ }
43860
+ function migrateSingleUserState(options) {
43861
+ const stateDir2 = resolve25(options.stateDir ?? process.env.NEGOTIUM_STATE_DIR ?? join42(homedir12(), ".negotium"));
43862
+ const source = options.sourcePrincipal?.trim() || CANONICAL_LOCAL_USER_ID;
43863
+ if (!source || /[/\\]|\.\./.test(source))
43864
+ throw new Error("Invalid source principal.");
43865
+ if (!options.deleteOtherUsers || !options.confirmed) {
43866
+ throw new Error("Migration requires --delete-other-users and --yes; other principals are permanently deleted.");
43867
+ }
43868
+ const markerPath = join42(stateDir2, ".migration-0.2.0-single-user.json");
43869
+ if (existsSync36(markerPath)) {
43870
+ const marker = JSON.parse(readFileSync27(markerPath, "utf8"));
43871
+ if (marker.id !== SINGLE_USER_MIGRATION_ID)
43872
+ throw new Error(`Unexpected migration marker at ${markerPath}.`);
43873
+ return {
43874
+ status: "already-completed",
43875
+ markerPath,
43876
+ sourcePrincipal: marker.sourcePrincipal ?? source,
43877
+ movedPaths: marker.movedPaths ?? 0,
43878
+ deletedPaths: marker.deletedPaths ?? 0,
43879
+ migratedTables: marker.migratedTables ?? []
43880
+ };
43881
+ }
43882
+ assertNoActiveNode(stateDir2);
43883
+ mkdirSync35(stateDir2, { recursive: true });
43884
+ const stagingRoot = join42(stateDir2, ".migration-0.2.0-staging");
43885
+ const journalPath = join42(stagingRoot, "journal.json");
43886
+ mkdirSync35(stagingRoot, { recursive: true, mode: 448 });
43887
+ let journal;
43888
+ if (existsSync36(journalPath)) {
43889
+ journal = JSON.parse(readFileSync27(journalPath, "utf8"));
43890
+ if (journal.sourcePrincipal !== source)
43891
+ throw new Error("Interrupted migration used a different source principal.");
43892
+ } else {
43893
+ const operations = [];
43894
+ const retainedTopicIds = selectedTopicIds(join42(stateDir2, "data", "sessions.db"), source);
43895
+ addDirectoryContents(operations, join42(stateDir2, "run"), join42(stateDir2, "runtime"), stagingRoot);
43896
+ addDirectoryContents(operations, join42(stateDir2, "bin"), join42(stateDir2, "binaries"), stagingRoot);
43897
+ for (const secret of ["node-control-token", "runtime-mcp-secret", "vault-master-key"]) {
43898
+ const sourcePath = join42(stateDir2, secret);
43899
+ if (existsSync36(sourcePath))
43900
+ operations.push({
43901
+ source: sourcePath,
43902
+ staged: join42(stagingRoot, randomUUID34()),
43903
+ destination: join42(stateDir2, "secrets", secret),
43904
+ state: "pending"
43905
+ });
43906
+ }
43907
+ const legacyVaultDb = join42(stateDir2, "data", "vault.db");
43908
+ if (existsSync36(legacyVaultDb)) {
43909
+ operations.push({
43910
+ source: legacyVaultDb,
43911
+ staged: join42(stagingRoot, randomUUID34()),
43912
+ destination: join42(stateDir2, "data", "vault", "vault.db"),
43913
+ state: "pending"
43914
+ });
43915
+ }
43916
+ for (const store of ["conversations", "tasks", "users"]) {
43917
+ const root = join42(stateDir2, "data", store);
43918
+ addDirectoryContents(operations, join42(root, source), root, stagingRoot);
43919
+ for (const owner of entries(root)) {
43920
+ const ownerPath = join42(root, owner);
43921
+ if (owner !== source && statSync19(ownerPath).isDirectory()) {
43922
+ operations.push({
43923
+ source: ownerPath,
43924
+ staged: join42(stagingRoot, randomUUID34()),
43925
+ state: "pending"
43926
+ });
43927
+ }
43928
+ }
43929
+ }
43930
+ for (const legacyWorkspaceDir of ["contexts", "dm", "sessions"]) {
43931
+ addDirectoryContents(operations, join42(stateDir2, "workspace", legacyWorkspaceDir), join42(stateDir2, "data", legacyWorkspaceDir), stagingRoot);
43932
+ }
43933
+ for (const topicId of entries(join42(stateDir2, "workspace", "topics"))) {
43934
+ const topicPath = join42(stateDir2, "workspace", "topics", topicId);
43935
+ if (retainedTopicIds && !retainedTopicIds.has(topicId)) {
43936
+ operations.push({
43937
+ source: topicPath,
43938
+ staged: join42(stagingRoot, randomUUID34()),
43939
+ state: "pending"
43940
+ });
43941
+ } else {
43942
+ addDirectoryContents(operations, join42(topicPath, "uploads"), join42(stateDir2, "data", "uploads", topicId), stagingRoot);
43943
+ }
43944
+ }
43945
+ if (retainedTopicIds) {
43946
+ for (const topicId of entries(join42(stateDir2, "workspace", "wiki"))) {
43947
+ if (retainedTopicIds.has(topicId))
43948
+ continue;
43949
+ operations.push({
43950
+ source: join42(stateDir2, "workspace", "wiki", topicId),
43951
+ staged: join42(stagingRoot, randomUUID34()),
43952
+ state: "pending"
43953
+ });
43954
+ }
43955
+ }
43956
+ const legacyBrowserRoot = join42(stateDir2, "workspace", "browser-profiles");
43957
+ const selectedProfiles = join42(legacyBrowserRoot, "profiles", oldOwnerDirectory(source));
43958
+ addDirectoryContents(operations, selectedProfiles, join42(stateDir2, "browser", "profiles"), stagingRoot);
43959
+ for (const owner of entries(join42(legacyBrowserRoot, "profiles"))) {
43960
+ const ownerPath = join42(legacyBrowserRoot, "profiles", owner);
43961
+ if (ownerPath !== selectedProfiles) {
43962
+ operations.push({
43963
+ source: ownerPath,
43964
+ staged: join42(stagingRoot, randomUUID34()),
43965
+ state: "pending"
43966
+ });
43967
+ }
43968
+ }
43969
+ for (const topicId of entries(legacyBrowserRoot).filter((name) => name !== "profiles")) {
43970
+ if (retainedTopicIds && !retainedTopicIds.has(topicId)) {
43971
+ operations.push({
43972
+ source: join42(legacyBrowserRoot, topicId),
43973
+ staged: join42(stagingRoot, randomUUID34()),
43974
+ state: "pending"
43975
+ });
43976
+ continue;
43977
+ }
43978
+ const profile = `legacy_${createHash11("sha256").update(topicId).digest("hex").slice(0, 12)}`;
43979
+ operations.push({
43980
+ source: join42(legacyBrowserRoot, topicId),
43981
+ staged: join42(stagingRoot, randomUUID34()),
43982
+ destination: join42(stateDir2, "browser", "profiles", profile),
43983
+ state: "pending"
43984
+ });
43985
+ }
43986
+ assertNoCollisions(operations);
43987
+ journal = {
43988
+ id: SINGLE_USER_MIGRATION_ID,
43989
+ sourcePrincipal: source,
43990
+ createdAt: new Date().toISOString(),
43991
+ phase: "filesystem",
43992
+ operations
43993
+ };
43994
+ writeJournal(journalPath, journal);
43995
+ }
43996
+ const dbPath = join42(stateDir2, "data", "sessions.db");
43997
+ const dbBackup = join42(stagingRoot, "sessions.db.rollback");
43998
+ const vaultDbPath = join42(stateDir2, "data", "vault", "vault.db");
43999
+ const vaultDbBackup = join42(stagingRoot, "vault.db.rollback");
44000
+ try {
44001
+ for (const operation of journal.operations) {
44002
+ if (operation.state === "pending" && !existsSync36(operation.source) && existsSync36(operation.staged)) {
44003
+ operation.state = "staged";
44004
+ }
44005
+ if (operation.state === "staged" && operation.destination && !existsSync36(operation.staged) && existsSync36(operation.destination)) {
44006
+ operation.state = "placed";
44007
+ }
44008
+ if (operation.state === "pending") {
44009
+ mkdirSync35(dirname23(operation.staged), { recursive: true });
44010
+ renameSync16(operation.source, operation.staged);
44011
+ operation.state = "staged";
44012
+ writeJournal(journalPath, journal);
44013
+ }
44014
+ if (operation.state === "staged" && operation.destination) {
44015
+ mkdirSync35(dirname23(operation.destination), { recursive: true });
44016
+ renameSync16(operation.staged, operation.destination);
44017
+ operation.state = "placed";
44018
+ writeJournal(journalPath, journal);
44019
+ }
44020
+ }
44021
+ journal.phase = "database";
44022
+ writeJournal(journalPath, journal);
44023
+ if (existsSync36(dbPath) && !existsSync36(dbBackup))
44024
+ copyFileSync6(dbPath, dbBackup);
44025
+ if (existsSync36(vaultDbPath) && !existsSync36(vaultDbBackup))
44026
+ copyFileSync6(vaultDbPath, vaultDbBackup);
44027
+ const masterKeyPath = join42(stateDir2, "secrets", "vault-master-key");
44028
+ const masterKey = existsSync36(masterKeyPath) ? readFileSync27(masterKeyPath, "utf8").trim() : undefined;
44029
+ const legacyBrowserRoot = join42(stateDir2, "workspace", "browser-profiles");
44030
+ const legacyTopicProfiles = journal.operations.filter((operation) => dirname23(operation.source) === legacyBrowserRoot && operation.destination).map((operation) => ({
44031
+ topicId: basename14(operation.source),
44032
+ profile: basename14(operation.destination)
44033
+ }));
44034
+ const migratedTables = migrateDatabase(dbPath, source, masterKey, legacyTopicProfiles);
44035
+ const migratedVaultTables = migrateDatabase(vaultDbPath, source, masterKey);
44036
+ for (const table of migratedVaultTables)
44037
+ if (!migratedTables.includes(table))
44038
+ migratedTables.push(table);
44039
+ mkdirSync35(join42(stateDir2, "secrets"), { recursive: true, mode: 448 });
44040
+ chmodSync8(join42(stateDir2, "secrets"), 448);
44041
+ for (const secret of ["node-control-token", "runtime-mcp-secret", "vault-master-key"]) {
44042
+ const path = join42(stateDir2, "secrets", secret);
44043
+ if (existsSync36(path))
44044
+ chmodSync8(path, 384);
44045
+ }
44046
+ const deletedPaths = journal.operations.filter((operation) => !operation.destination).length;
44047
+ const movedPaths = journal.operations.length - deletedPaths;
44048
+ const marker = {
44049
+ id: SINGLE_USER_MIGRATION_ID,
44050
+ version: "0.2.0",
44051
+ completedAt: new Date().toISOString(),
44052
+ sourcePrincipal: source,
44053
+ canonicalPrincipal: CANONICAL_LOCAL_USER_ID,
44054
+ movedPaths,
44055
+ deletedPaths,
44056
+ migratedTables
44057
+ };
44058
+ for (const store of ["conversations", "tasks", "users"]) {
44059
+ rmSync11(join42(stateDir2, "data", store, source), { recursive: true, force: true });
44060
+ }
44061
+ rmSync11(join42(stateDir2, "workspace", "browser-profiles"), { recursive: true, force: true });
44062
+ writeFileSync25(markerPath, `${JSON.stringify(marker, null, 2)}
44063
+ `, { mode: 384, flag: "wx" });
44064
+ journal.phase = "complete";
44065
+ rmSync11(stagingRoot, { recursive: true, force: true });
44066
+ return {
44067
+ status: "completed",
44068
+ markerPath,
44069
+ sourcePrincipal: source,
44070
+ movedPaths,
44071
+ deletedPaths,
44072
+ migratedTables
44073
+ };
44074
+ } catch (error2) {
44075
+ if (existsSync36(dbBackup))
44076
+ copyFileSync6(dbBackup, dbPath);
44077
+ if (existsSync36(vaultDbBackup))
44078
+ copyFileSync6(vaultDbBackup, vaultDbPath);
44079
+ rollbackFilesystem(journal, journalPath);
44080
+ throw error2;
44081
+ }
44082
+ }
44083
+ var CANONICAL_LOCAL_USER_ID = "local", SINGLE_USER_MIGRATION_ID = "negotium-0.2.0-single-user";
44084
+ var init_single_user = __esm(async () => {
44085
+ init_vault_crypto();
44086
+ await init_sqlite();
44087
+ });
44088
+
44089
+ // ../cli/src/commands/migrate.ts
44090
+ var exports_migrate = {};
44091
+ __export(exports_migrate, {
44092
+ migrateCommand: () => migrateCommand
44093
+ });
44094
+ function option3(args, name) {
44095
+ const prefix = `--${name}=`;
44096
+ return args.find((arg) => arg.startsWith(prefix))?.slice(prefix.length);
44097
+ }
44098
+ function migrateCommand(args) {
44099
+ if (args[0] !== "single-user") {
44100
+ throw new Error("usage: negotium migrate single-user [--source=local] --delete-other-users --yes");
44101
+ }
44102
+ const result = migrateSingleUserState({
44103
+ sourcePrincipal: option3(args, "source"),
44104
+ deleteOtherUsers: args.includes("--delete-other-users"),
44105
+ confirmed: args.includes("--yes")
44106
+ });
44107
+ console.log(`${result.status}: ${result.markerPath}`);
44108
+ }
44109
+ var init_migrate = __esm(async () => {
44110
+ await init_single_user();
44111
+ });
44112
+
43660
44113
  // ../cli/src/commands/topics.ts
43661
44114
  var exports_topics = {};
43662
44115
  __export(exports_topics, {
@@ -43758,12 +44211,12 @@ function vaultCommand(args) {
43758
44211
  switch (sub) {
43759
44212
  case undefined:
43760
44213
  case "list": {
43761
- const entries = vaultListWithValues(DEFAULT_USER);
43762
- if (entries.length === 0) {
44214
+ const entries2 = vaultListWithValues(DEFAULT_USER);
44215
+ if (entries2.length === 0) {
43763
44216
  console.log("vault is empty \u2014 `negotium vault set MY_KEY <value> [description]`");
43764
44217
  return;
43765
44218
  }
43766
- for (const entry of entries) {
44219
+ for (const entry of entries2) {
43767
44220
  console.log(`${entry.key} ${entry.description || ""}`.trimEnd());
43768
44221
  }
43769
44222
  return;
@@ -44087,6 +44540,12 @@ var CLI_COMMANDS = [
44087
44540
  description: "bootstrap ~/.negotium and check agent auth",
44088
44541
  group: "Setup"
44089
44542
  },
44543
+ {
44544
+ name: "migrate",
44545
+ usage: "migrate single-user [--source=local] --delete-other-users --yes",
44546
+ description: "explicitly migrate a 0.1 state tree to the 0.2 single-user layout",
44547
+ group: "Setup"
44548
+ },
44090
44549
  {
44091
44550
  name: "serve",
44092
44551
  usage: "serve [otium]",
@@ -44193,7 +44652,7 @@ async function runCanonicalNode(port) {
44193
44652
  });
44194
44653
  console.log(`negotium node listening on 127.0.0.1:${node.port} (ctrl-c to stop)`);
44195
44654
  await node.completed;
44196
- await new Promise((resolve25) => setImmediate(resolve25));
44655
+ await new Promise((resolve26) => setImmediate(resolve26));
44197
44656
  process.exit(0);
44198
44657
  }
44199
44658
  async function stopAdapter(name) {
@@ -44216,6 +44675,11 @@ switch (command) {
44216
44675
  initCommand2();
44217
44676
  break;
44218
44677
  }
44678
+ case "migrate": {
44679
+ const { migrateCommand: migrateCommand2 } = await init_migrate().then(() => exports_migrate);
44680
+ migrateCommand2(args);
44681
+ break;
44682
+ }
44219
44683
  case "-v":
44220
44684
  case "--version": {
44221
44685
  const { NEGOTIUM_VERSION: NEGOTIUM_VERSION2 } = await Promise.resolve().then(() => exports_version);
@@ -44324,4 +44788,4 @@ switch (command) {
44324
44788
  }
44325
44789
  }
44326
44790
 
44327
- //# debugId=90FDEC7EC6CF014764756E2164756E21
44791
+ //# debugId=0E990AB8BEC23DD564756E2164756E21