negotium 0.2.2 → 0.2.4

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 (70) hide show
  1. package/dist/agent-helpers.js +3117 -2915
  2. package/dist/agent-helpers.js.map +38 -37
  3. package/dist/background-bash.js.map +1 -1
  4. package/dist/browser-runtime.js +137 -21
  5. package/dist/browser-runtime.js.map +10 -9
  6. package/dist/{chunk-kc5kmr95.js → chunk-1s9ryz8g.js} +1195 -21
  7. package/dist/chunk-1s9ryz8g.js.map +35 -0
  8. package/dist/hosted-agent.js +60 -48
  9. package/dist/hosted-agent.js.map +10 -10
  10. package/dist/main.js +3038 -3357
  11. package/dist/main.js.map +35 -35
  12. package/dist/mcp-factories.js +6227 -6021
  13. package/dist/mcp-factories.js.map +40 -39
  14. package/dist/prompts.js.map +1 -1
  15. package/dist/query-runtime.js +22 -2
  16. package/dist/query-runtime.js.map +4 -4
  17. package/dist/registry.js +169 -213
  18. package/dist/registry.js.map +8 -9
  19. package/dist/rollout.js +1 -1
  20. package/dist/runtime/src/agents/codex-app-server.ts +136 -0
  21. package/dist/runtime/src/agents/codex-native-multi-agent.ts +1 -1
  22. package/dist/runtime/src/agents/codex-provider.ts +21 -11
  23. package/dist/runtime/src/agents/codex-registry.ts +9 -17
  24. package/dist/runtime/src/agents/contracts.ts +2 -0
  25. package/dist/runtime/src/agents/execution-host.ts +6 -0
  26. package/dist/runtime/src/agents/fork.ts +24 -18
  27. package/dist/runtime/src/agents/idle-archiver.ts +7 -2
  28. package/dist/runtime/src/agents/index.ts +2 -1
  29. package/dist/runtime/src/agents/maestro-registry.ts +26 -7
  30. package/dist/runtime/src/agents/memory-archive-policy.ts +20 -0
  31. package/dist/runtime/src/agents/rollout/codex.ts +6 -5
  32. package/dist/runtime/src/agents/rollout/shared.ts +3 -5
  33. package/dist/runtime/src/mcp/factories/session-comm.ts +12 -0
  34. package/dist/runtime/src/mcp/session-comm/default-host.ts +16 -0
  35. package/dist/runtime/src/mcp/session-comm/server.ts +25 -0
  36. package/dist/runtime/src/platform/config.ts +4 -1
  37. package/dist/runtime/src/platform/playwright/manager.ts +57 -1
  38. package/dist/runtime/src/platform/playwright/profile-management.ts +33 -0
  39. package/dist/runtime/src/platform/playwright/public-runtime.ts +5 -0
  40. package/dist/runtime/src/platform/playwright/vault-broker.ts +9 -10
  41. package/dist/runtime/src/storage/browser-profiles.ts +35 -10
  42. package/dist/runtime/src/storage/storage-host.ts +31 -5
  43. package/dist/runtime/src/storage/vault.ts +30 -2
  44. package/dist/runtime/src/topics/derive.ts +38 -2
  45. package/dist/runtime/src/topics/lifecycle.ts +12 -2
  46. package/dist/runtime/src/version.ts +1 -1
  47. package/dist/runtime-helpers.js +22 -2
  48. package/dist/runtime-helpers.js.map +4 -4
  49. package/dist/storage.js +22 -2
  50. package/dist/storage.js.map +4 -4
  51. package/dist/types/packages/core/src/agents/codex-app-server.d.ts +13 -0
  52. package/dist/types/packages/core/src/agents/codex-native-multi-agent.d.ts +1 -0
  53. package/dist/types/packages/core/src/agents/contracts.d.ts +2 -0
  54. package/dist/types/packages/core/src/agents/execution-host.d.ts +2 -0
  55. package/dist/types/packages/core/src/agents/fork.d.ts +8 -7
  56. package/dist/types/packages/core/src/agents/memory-archive-policy.d.ts +10 -0
  57. package/dist/types/packages/core/src/agents/rollout/codex.d.ts +4 -2
  58. package/dist/types/packages/core/src/mcp/factories/session-comm.d.ts +1 -0
  59. package/dist/types/packages/core/src/platform/playwright/manager.d.ts +15 -0
  60. package/dist/types/packages/core/src/platform/playwright/profile-management.d.ts +8 -0
  61. package/dist/types/packages/core/src/platform/playwright/public-runtime.d.ts +2 -1
  62. package/dist/types/packages/core/src/storage/browser-profiles.d.ts +3 -1
  63. package/dist/types/packages/core/src/storage/storage-host.d.ts +1 -0
  64. package/dist/types/packages/core/src/storage/vault.d.ts +8 -0
  65. package/dist/types/packages/core/src/version.d.ts +1 -1
  66. package/dist/vault.js +19 -3
  67. package/dist/vault.js.map +4 -4
  68. package/package.json +2 -2
  69. package/dist/chunk-kc5kmr95.js.map +0 -19
  70. package/dist/runtime/src/migration/single-user.ts +0 -805
@@ -67,16 +67,42 @@ function defaultSessionsDatabasePath(): string {
67
67
  return envPath("SESSIONS_DB_PATH", join(resolveStorageDataDir(), "sessions.db"));
68
68
  }
69
69
 
70
- function initializeDatabase(database: InternalStorageDatabase): void {
70
+ const SQLITE_INIT_RETRY_MS = 25;
71
+ const SQLITE_INIT_TIMEOUT_MS = 5_000;
72
+ const SQLITE_INIT_SLEEP = new Int32Array(new SharedArrayBuffer(Int32Array.BYTES_PER_ELEMENT));
73
+
74
+ function isSqliteBusy(error: unknown): boolean {
75
+ const message = error instanceof Error ? error.message : String(error);
76
+ return /database is (?:locked|busy)|SQLITE_(?:BUSY|LOCKED)/i.test(message);
77
+ }
78
+
79
+ function execWithBusyRetry(
80
+ database: InternalStorageDatabase,
81
+ sql: string,
82
+ timeoutMs = SQLITE_INIT_TIMEOUT_MS,
83
+ ): void {
84
+ const deadline = Date.now() + timeoutMs;
85
+ while (true) {
86
+ try {
87
+ database.exec(sql);
88
+ return;
89
+ } catch (error) {
90
+ if (!isSqliteBusy(error) || Date.now() >= deadline) throw error;
91
+ Atomics.wait(SQLITE_INIT_SLEEP, 0, 0, Math.min(SQLITE_INIT_RETRY_MS, deadline - Date.now()));
92
+ }
93
+ }
94
+ }
95
+
96
+ export function initializeDatabase(database: InternalStorageDatabase): void {
71
97
  // busy_timeout FIRST. Switching to WAL needs a brief exclusive lock, so when
72
98
  // two processes open the same database at the same moment — the node daemon
73
99
  // and an adapter, or two MCP servers — one of them hits SQLITE_BUSY. With the
74
100
  // timeout configured afterwards there was no retry budget in effect for the
75
- // statement that needed it most, and the loser died with "database is locked"
76
- // during startup. Measured with four processes racing one fresh file: 60/100
77
- // opens failed in the old order, 0/100 with the timeout set first.
101
+ // statement that needed it most. Older Bun releases can still surface
102
+ // SQLITE_BUSY immediately for journal_mode, so keep a bounded host-level
103
+ // retry around that one exclusive transition as well.
78
104
  database.exec("PRAGMA busy_timeout = 5000");
79
- database.exec("PRAGMA journal_mode = WAL");
105
+ execWithBusyRetry(database, "PRAGMA journal_mode = WAL");
80
106
  database.exec("PRAGMA foreign_keys = ON");
81
107
  database.exec("PRAGMA wal_autocheckpoint = 1000");
82
108
  try {
@@ -143,6 +143,33 @@ export function vaultListWithValues(userId: string): VaultEntryWithValue[] {
143
143
  }));
144
144
  }
145
145
 
146
+ /**
147
+ * Return only rows that can still be authenticated with the active master key.
148
+ *
149
+ * This is intentionally limited to redaction indexes: one damaged credential
150
+ * must not disable unrelated tools, while direct access to that credential
151
+ * continues to fail closed through vaultGetValue().
152
+ */
153
+ export function vaultListDecryptableValues(userId: string): VaultEntryWithValue[] {
154
+ const rows = activeVaultDatabase()
155
+ .prepare("SELECT key, description, value FROM vault WHERE user_id = ? ORDER BY key")
156
+ .all(userId) as VaultEntryWithValue[];
157
+ const entries: VaultEntryWithValue[] = [];
158
+ for (const row of rows) {
159
+ try {
160
+ entries.push({
161
+ key: row.key,
162
+ description: row.description,
163
+ value: decryptRow(userId, row.key, row.value),
164
+ });
165
+ } catch {
166
+ // A value that cannot be authenticated cannot be exposed by substitution,
167
+ // so there is no plaintext form available to add to the redaction index.
168
+ }
169
+ }
170
+ return entries;
171
+ }
172
+
146
173
  export function vaultSet(userId: string, key: string, value: string, description = ""): void {
147
174
  const normalizedKey = normalizeVaultKey(key);
148
175
  activeVaultDatabase()
@@ -203,10 +230,11 @@ export interface VaultSubstitutionResult {
203
230
 
204
231
  /** Replace {{KEY}} placeholders and report which credentials were consumed. */
205
232
  export function vaultSubstituteDetailed(userId: string, text: string): VaultSubstitutionResult {
206
- const entries = new Map(vaultListWithValues(userId).map((entry) => [entry.key, entry.value]));
233
+ const entries = new Map<string, string | undefined>();
207
234
  const usedKeys = new Set<string>();
208
235
  const substituted = text.replace(/\{\{([^}]+)\}\}/g, (match, rawKey: string) => {
209
236
  const key = normalizeVaultKey(rawKey);
237
+ if (!entries.has(key)) entries.set(key, vaultGetValue(userId, key));
210
238
  const value = entries.get(key);
211
239
  if (value === undefined) return match;
212
240
  usedKeys.add(key);
@@ -248,7 +276,7 @@ function encodedSecretForms(value: string): string[] {
248
276
 
249
277
  /** Scrub raw and common encoded forms before tool output reaches a model. */
250
278
  export function redactVaultSecrets(userId: string, text: string): string {
251
- const candidates = vaultListWithValues(userId)
279
+ const candidates = vaultListDecryptableValues(userId)
252
280
  .flatMap((entry) => encodedSecretForms(entry.value).map((form) => ({ form, key: entry.key })))
253
281
  .sort((a, b) => b.form.length - a.form.length || a.key.localeCompare(b.key));
254
282
  if (candidates.length === 0) return text;
@@ -9,7 +9,7 @@
9
9
 
10
10
  import { createHash, randomUUID } from "node:crypto";
11
11
  import { mkdirSync, rmSync, unlinkSync } from "node:fs";
12
- import { cleanupAgentFork, type ForkHandle } from "#agents/fork";
12
+ import { cleanupAgentFork, type ForkHandle, forkAgentSession } from "#agents/fork";
13
13
  import { resolveCompactionExecution, resolveModelForAgent } from "#agents/model-catalog";
14
14
  import { getRegistry, getRegistryOperations } from "#agents/registry";
15
15
  import { WsHub } from "#bus";
@@ -26,6 +26,7 @@ import { getApiTopicConfig, setApiTopicConfig } from "#storage/api-topic-config"
26
26
  import {
27
27
  findTopicTitleConflict,
28
28
  getTopic,
29
+ getTopicSessionId,
29
30
  inferTopicKind,
30
31
  isTopicVisible,
31
32
  listTopics,
@@ -312,6 +313,11 @@ async function createDerivedTopicImpl(
312
313
  (subagent?.agent ? undefined : sourceConfig?.model) ??
313
314
  derived.defaultModel;
314
315
  const rolloutModel = resolveModelForAgent(agent, requestedRolloutModel, registry);
316
+ const sourceRolloutModel = resolveModelForAgent(
317
+ agent,
318
+ sourceConfig?.model ?? topic.defaultModel,
319
+ registry,
320
+ );
315
321
  const requestedRolloutEffort = subagent?.agent
316
322
  ? registry.defaultEffort
317
323
  : (sourceConfig?.effort ?? derived.defaultEffort);
@@ -323,7 +329,37 @@ async function createDerivedTopicImpl(
323
329
 
324
330
  if (copyHistory) {
325
331
  if (!forkSnapshot) throw new Error("fork snapshot was not captured");
326
- const compactionRequired = shouldCompactForkEntries(forkSnapshot.entries);
332
+ const parentSessionId = getTopicSessionId(sourceTopicId);
333
+ const cachePreservingNativeFork =
334
+ (agent === "codex" || agent === "maestro") && sourceRolloutModel === rolloutModel;
335
+ if (cachePreservingNativeFork && parentSessionId) {
336
+ try {
337
+ rollbackHandle = await forkAgentSession({
338
+ agent,
339
+ parentSessionId,
340
+ cwd,
341
+ userId,
342
+ topicName: topic.title,
343
+ title,
344
+ model: rolloutModel,
345
+ ...(rolloutEffort ? { effort: rolloutEffort } : {}),
346
+ });
347
+ sessionId = rollbackHandle.forkId;
348
+ logger.info(
349
+ { sourceTopicId, derivedTopicId: derived.id, parentSessionId, sessionId },
350
+ "createDerivedTopic: native provider fork materialized",
351
+ );
352
+ } catch (error) {
353
+ logger.warn(
354
+ { err: error, sourceTopicId, derivedTopicId: derived.id, parentSessionId },
355
+ "createDerivedTopic: native provider fork failed; using snapshot fallback",
356
+ );
357
+ }
358
+ }
359
+ // Successful native forks retain the exact provider prefix. Once that
360
+ // path is unavailable, cache continuity is already lost; keep a small
361
+ // snapshot verbatim and compact only an oversized fallback.
362
+ const compactionRequired = !sessionId && shouldCompactForkEntries(forkSnapshot.entries);
327
363
  if (compactionRequired) {
328
364
  try {
329
365
  compactedForkEntries = await createCompactedRolloutEntries(
@@ -9,7 +9,10 @@ import { rmSync } from "node:fs";
9
9
  import { runArchiverTurn } from "#agents/archiver";
10
10
  import { cancelIdleArchiveForTopic } from "#agents/idle-archiver";
11
11
  import { cancelSubagentWatchForDeletedTopic } from "#agents/mcp-tools/spawn-subagent";
12
- import { MIN_MEMORY_ARCHIVE_EXCHANGES } from "#agents/memory-archive-policy";
12
+ import {
13
+ MIN_MEMORY_ARCHIVE_EXCHANGES,
14
+ meetsMemoryArchiveExchangeThreshold,
15
+ } from "#agents/memory-archive-policy";
13
16
  import { type PurgeSessionRef, purgeTopicLogs } from "#agents/topic-cleanup";
14
17
  import { WsHub } from "#bus";
15
18
  import { killBgBash } from "#platform/background-bash/manager";
@@ -238,7 +241,14 @@ async function deleteTopicCascadeImpl(
238
241
  }
239
242
  }
240
243
 
241
- if (archived && archived.exchangeCount >= MIN_MEMORY_ARCHIVE_EXCHANGES) {
244
+ if (
245
+ archived &&
246
+ meetsMemoryArchiveExchangeThreshold(
247
+ archived.exchangeCount,
248
+ MIN_MEMORY_ARCHIVE_EXCHANGES,
249
+ topic,
250
+ )
251
+ ) {
242
252
  try {
243
253
  const memoryTopic = getTopicMemoryOrigin(topicId) ?? topic;
244
254
  runArchiverTurn({
@@ -1 +1 @@
1
- export const NEGOTIUM_VERSION = "0.2.2";
1
+ export const NEGOTIUM_VERSION = "0.2.4";
@@ -695,9 +695,29 @@ function defaultDataDir() {
695
695
  function defaultSessionsDatabasePath() {
696
696
  return envPath("SESSIONS_DB_PATH", join2(resolveStorageDataDir(), "sessions.db"));
697
697
  }
698
+ var SQLITE_INIT_RETRY_MS = 25;
699
+ var SQLITE_INIT_TIMEOUT_MS = 5000;
700
+ var SQLITE_INIT_SLEEP = new Int32Array(new SharedArrayBuffer(Int32Array.BYTES_PER_ELEMENT));
701
+ function isSqliteBusy(error) {
702
+ const message = error instanceof Error ? error.message : String(error);
703
+ return /database is (?:locked|busy)|SQLITE_(?:BUSY|LOCKED)/i.test(message);
704
+ }
705
+ function execWithBusyRetry(database, sql, timeoutMs = SQLITE_INIT_TIMEOUT_MS) {
706
+ const deadline = Date.now() + timeoutMs;
707
+ while (true) {
708
+ try {
709
+ database.exec(sql);
710
+ return;
711
+ } catch (error) {
712
+ if (!isSqliteBusy(error) || Date.now() >= deadline)
713
+ throw error;
714
+ Atomics.wait(SQLITE_INIT_SLEEP, 0, 0, Math.min(SQLITE_INIT_RETRY_MS, deadline - Date.now()));
715
+ }
716
+ }
717
+ }
698
718
  function initializeDatabase(database) {
699
719
  database.exec("PRAGMA busy_timeout = 5000");
700
- database.exec("PRAGMA journal_mode = WAL");
720
+ execWithBusyRetry(database, "PRAGMA journal_mode = WAL");
701
721
  database.exec("PRAGMA foreign_keys = ON");
702
722
  database.exec("PRAGMA wal_autocheckpoint = 1000");
703
723
  try {
@@ -1114,4 +1134,4 @@ export {
1114
1134
  CLAUDE_EFFORT_VALUES
1115
1135
  };
1116
1136
 
1117
- //# debugId=0849C2E75E1422A764756E2164756E21
1137
+ //# debugId=16168DE340D196C364756E2164756E21