negotium 0.2.3 → 0.2.5

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 (99) hide show
  1. package/dist/agent-helpers.js +3366 -3048
  2. package/dist/agent-helpers.js.map +49 -48
  3. package/dist/background-bash.js +1 -3
  4. package/dist/background-bash.js.map +4 -4
  5. package/dist/browser-runtime.js +137 -23
  6. package/dist/browser-runtime.js.map +11 -10
  7. package/dist/chunk-qmbcbhyy.js +2407 -0
  8. package/dist/chunk-qmbcbhyy.js.map +35 -0
  9. package/dist/hosted-agent.js +56 -39
  10. package/dist/hosted-agent.js.map +13 -13
  11. package/dist/main.js +3276 -2808
  12. package/dist/main.js.map +54 -51
  13. package/dist/mcp-factories.js +6471 -6148
  14. package/dist/mcp-factories.js.map +49 -48
  15. package/dist/prompts.js +1 -3
  16. package/dist/prompts.js.map +4 -4
  17. package/dist/query-runtime.js +22 -4
  18. package/dist/query-runtime.js.map +6 -6
  19. package/dist/registry.js +169 -213
  20. package/dist/registry.js.map +8 -9
  21. package/dist/rollout.js +1 -1
  22. package/dist/runtime/src/agents/api-topic-agent-switch.ts +6 -12
  23. package/dist/runtime/src/agents/claude-provider.ts +2 -2
  24. package/dist/runtime/src/agents/codex-app-server.ts +136 -0
  25. package/dist/runtime/src/agents/codex-native-multi-agent.ts +1 -1
  26. package/dist/runtime/src/agents/codex-provider.ts +22 -11
  27. package/dist/runtime/src/agents/codex-registry.ts +9 -17
  28. package/dist/runtime/src/agents/contracts.ts +2 -0
  29. package/dist/runtime/src/agents/execution-host.ts +6 -0
  30. package/dist/runtime/src/agents/fork.ts +24 -18
  31. package/dist/runtime/src/agents/idle-archiver.ts +7 -2
  32. package/dist/runtime/src/agents/index.ts +21 -15
  33. package/dist/runtime/src/agents/maestro-provider.ts +1 -1
  34. package/dist/runtime/src/agents/maestro-registry.ts +26 -7
  35. package/dist/runtime/src/agents/memory-archive-policy.ts +20 -0
  36. package/dist/runtime/src/agents/rollout/codex.ts +31 -23
  37. package/dist/runtime/src/agents/self-config-core.ts +2 -4
  38. package/dist/runtime/src/agents/topic-agent-switch.ts +4 -10
  39. package/dist/runtime/src/application/submit-runtime-gateway-turn.ts +77 -19
  40. package/dist/runtime/src/application/switch-topic-model.ts +7 -10
  41. package/dist/runtime/src/index.ts +4 -0
  42. package/dist/runtime/src/mcp/factories/session-comm.ts +12 -0
  43. package/dist/runtime/src/mcp/session-comm/default-host.ts +16 -0
  44. package/dist/runtime/src/mcp/session-comm/server.ts +25 -0
  45. package/dist/runtime/src/mcp/wiki-server.ts +19 -12
  46. package/dist/runtime/src/platform/config.ts +4 -3
  47. package/dist/runtime/src/platform/log-rotation.ts +45 -0
  48. package/dist/runtime/src/platform/mcp-config.ts +9 -3
  49. package/dist/runtime/src/platform/playwright/manager.ts +57 -1
  50. package/dist/runtime/src/platform/playwright/profile-management.ts +33 -0
  51. package/dist/runtime/src/platform/playwright/public-runtime.ts +5 -0
  52. package/dist/runtime/src/platform/playwright/vault-broker.ts +9 -10
  53. package/dist/runtime/src/query/active-rooms.ts +7 -1
  54. package/dist/runtime/src/runtime/errors.ts +1 -0
  55. package/dist/runtime/src/runtime/turn-event-stream.ts +46 -1
  56. package/dist/runtime/src/runtime/turn-runner.ts +176 -100
  57. package/dist/runtime/src/runtime/user-turn-envelope.ts +19 -0
  58. package/dist/runtime/src/storage/browser-profiles.ts +35 -10
  59. package/dist/runtime/src/storage/runtime-gateway-submissions.ts +12 -3
  60. package/dist/runtime/src/storage/runtime-turn-requests.ts +68 -7
  61. package/dist/runtime/src/storage/storage-host.ts +31 -5
  62. package/dist/runtime/src/storage/vault.ts +30 -2
  63. package/dist/runtime/src/topics/derive.ts +38 -2
  64. package/dist/runtime/src/topics/lifecycle.ts +12 -2
  65. package/dist/runtime/src/types.ts +2 -0
  66. package/dist/runtime/src/version.ts +1 -1
  67. package/dist/runtime-helpers.js +22 -4
  68. package/dist/runtime-helpers.js.map +5 -5
  69. package/dist/storage.js +22 -2
  70. package/dist/storage.js.map +5 -5
  71. package/dist/types/packages/core/src/agents/api-topic-agent-switch.d.ts +1 -1
  72. package/dist/types/packages/core/src/agents/codex-app-server.d.ts +13 -0
  73. package/dist/types/packages/core/src/agents/codex-native-multi-agent.d.ts +1 -0
  74. package/dist/types/packages/core/src/agents/contracts.d.ts +2 -0
  75. package/dist/types/packages/core/src/agents/execution-host.d.ts +2 -0
  76. package/dist/types/packages/core/src/agents/fork.d.ts +8 -7
  77. package/dist/types/packages/core/src/agents/memory-archive-policy.d.ts +10 -0
  78. package/dist/types/packages/core/src/agents/rollout/codex.d.ts +4 -2
  79. package/dist/types/packages/core/src/agents/self-config-core.d.ts +1 -1
  80. package/dist/types/packages/core/src/mcp/factories/session-comm.d.ts +1 -0
  81. package/dist/types/packages/core/src/platform/config.d.ts +0 -1
  82. package/dist/types/packages/core/src/platform/mcp-config.d.ts +3 -0
  83. package/dist/types/packages/core/src/platform/playwright/manager.d.ts +15 -0
  84. package/dist/types/packages/core/src/platform/playwright/profile-management.d.ts +8 -0
  85. package/dist/types/packages/core/src/platform/playwright/public-runtime.d.ts +2 -1
  86. package/dist/types/packages/core/src/query/active-rooms.d.ts +7 -1
  87. package/dist/types/packages/core/src/runtime/turn-runner.d.ts +42 -1
  88. package/dist/types/packages/core/src/runtime/user-turn-envelope.d.ts +7 -0
  89. package/dist/types/packages/core/src/storage/browser-profiles.d.ts +3 -1
  90. package/dist/types/packages/core/src/storage/runtime-turn-requests.d.ts +17 -0
  91. package/dist/types/packages/core/src/storage/storage-host.d.ts +1 -0
  92. package/dist/types/packages/core/src/storage/vault.d.ts +8 -0
  93. package/dist/types/packages/core/src/types.d.ts +2 -0
  94. package/dist/types/packages/core/src/version.d.ts +1 -1
  95. package/dist/vault.js +19 -5
  96. package/dist/vault.js.map +5 -5
  97. package/package.json +2 -2
  98. package/dist/chunk-hsqgb2hw.js +0 -1223
  99. package/dist/chunk-hsqgb2hw.js.map +0 -19
@@ -2,7 +2,7 @@ import { randomUUID } from "node:crypto";
2
2
  import {
3
3
  flattenUserTurnAttachments,
4
4
  legacyUserTurnEnvelope,
5
- renderUserPromptBatch,
5
+ renderUserTurnBatch,
6
6
  type UserTurnEnvelope,
7
7
  } from "#runtime/user-turn-envelope";
8
8
  import { db } from "#storage/forum-db";
@@ -31,10 +31,14 @@ export interface RuntimeUserTurnExecution {
31
31
  bridgeSessionFromHistory?: boolean;
32
32
  peerBridge?: PeerRuntimeBridgeContext;
33
33
  from?: string;
34
+ /** Vault namespace for this turn, independent from the local execution principal. */
35
+ vaultUserId?: string;
34
36
  /** Newly accepted user texts not yet recorded in the unified conversation log. */
35
37
  conversationPrompts?: string[];
36
38
  /** Number of leading userMessages already present in the unified conversation log. */
37
39
  loggedUserMessageCount?: number;
40
+ /** Native session whose provider output proves it accepted this request's user turn. */
41
+ providerSessionId?: string;
38
42
  /** Request ids whose ordered messages were folded into this replacement. */
39
43
  supersededRequestIds?: string[];
40
44
  }
@@ -175,7 +179,11 @@ function rowToRequest(row: RuntimeUserTurnRequestRow): RuntimeUserTurnRequest {
175
179
  (Array.isArray((item as UserTurnEnvelope).attachments) &&
176
180
  (item as UserTurnEnvelope).attachments?.every(
177
181
  (attachment) => typeof attachment === "string",
178
- ))),
182
+ ))) &&
183
+ ((item as UserTurnEnvelope).actorUserId === undefined ||
184
+ typeof (item as UserTurnEnvelope).actorUserId === "string") &&
185
+ ((item as UserTurnEnvelope).actorLabel === undefined ||
186
+ typeof (item as UserTurnEnvelope).actorLabel === "string"),
179
187
  )
180
188
  ) {
181
189
  userMessages = parsed as UserTurnEnvelope[];
@@ -285,6 +293,8 @@ export function mergeRuntimeUserTurnRequest(input: {
285
293
  execution: RuntimeUserTurnExecution;
286
294
  topicEpoch: number;
287
295
  alreadyIncludedRequestIds?: string[];
296
+ /** Requests already committed to the provider's native session; keep their lineage, not prompts. */
297
+ omitRequestIds?: string[];
288
298
  }): { requestId: string; supersededRequestIds: string[] } {
289
299
  const now = Date.now();
290
300
  return db
@@ -295,14 +305,23 @@ export function mergeRuntimeUserTurnRequest(input: {
295
305
  )
296
306
  .all(input.topicId);
297
307
  const previous = rows.map(rowToRequest);
308
+ const omittedRequestIds = new Set([
309
+ ...(input.omitRequestIds ?? []),
310
+ ...previous
311
+ .filter((request) => Boolean(request.execution?.providerSessionId))
312
+ .map((request) => request.requestId),
313
+ ]);
314
+ const carried = previous.filter((request) => !omittedRequestIds.has(request.requestId));
298
315
  const alreadyIncludedRequestIds = new Set(input.alreadyIncludedRequestIds ?? []);
299
- const alreadyIncludedMessages = previous
316
+ const alreadyIncludedMessages = carried
300
317
  .filter((request) => alreadyIncludedRequestIds.has(request.requestId))
301
318
  .flatMap((request) => request.userMessages);
302
319
  const includedPrefixMatches = alreadyIncludedMessages.every((message, index) => {
303
320
  const candidate = input.userMessages[index];
304
321
  return (
305
322
  candidate?.prompt === message.prompt &&
323
+ candidate.actorUserId === message.actorUserId &&
324
+ candidate.actorLabel === message.actorLabel &&
306
325
  JSON.stringify(candidate.attachments ?? []) === JSON.stringify(message.attachments ?? [])
307
326
  );
308
327
  });
@@ -310,7 +329,7 @@ export function mergeRuntimeUserTurnRequest(input: {
310
329
  ? alreadyIncludedMessages.length
311
330
  : 0;
312
331
  const userMessages = [
313
- ...previous.flatMap((request) => request.userMessages),
332
+ ...carried.flatMap((request) => request.userMessages),
314
333
  ...input.userMessages.slice(alreadyIncludedMessageCount),
315
334
  ];
316
335
  const incomingLoggedCount = Math.min(
@@ -318,7 +337,7 @@ export function mergeRuntimeUserTurnRequest(input: {
318
337
  input.userMessages.length,
319
338
  );
320
339
  const loggedUserMessageCount =
321
- previous.reduce((count, request) => count + loggedMessageCount(request), 0) +
340
+ carried.reduce((count, request) => count + loggedMessageCount(request), 0) +
322
341
  Math.max(0, incomingLoggedCount - alreadyIncludedMessageCount);
323
342
  const execution: RuntimeUserTurnExecution = {
324
343
  ...input.execution,
@@ -335,7 +354,7 @@ export function mergeRuntimeUserTurnRequest(input: {
335
354
  .slice(loggedUserMessageCount)
336
355
  .map((message) => message.prompt),
337
356
  };
338
- const sessionBase = previous.find(
357
+ const sessionBase = carried.find(
339
358
  (request) => request.execution?.sessionIdSpecified,
340
359
  )?.execution;
341
360
  if (sessionBase?.sessionIdSpecified) {
@@ -355,7 +374,7 @@ export function mergeRuntimeUserTurnRequest(input: {
355
374
  input.requestId,
356
375
  input.topicId,
357
376
  input.userId,
358
- renderUserPromptBatch(userMessages.map((message) => message.prompt)),
377
+ renderUserTurnBatch(userMessages),
359
378
  JSON.stringify(userMessages),
360
379
  attachments?.length ? JSON.stringify(attachments) : null,
361
380
  input.allowAutoContinue ? 1 : 0,
@@ -371,6 +390,46 @@ export function mergeRuntimeUserTurnRequest(input: {
371
390
  .immediate();
372
391
  }
373
392
 
393
+ /**
394
+ * Record that provider output proves the native session contains this durable
395
+ * turn. A session-id event alone is insufficient: some providers publish it
396
+ * before appending the user prompt. This marker lets a different runtime
397
+ * process omit only a demonstrably committed prompt when it merges a steering
398
+ * message after preemption.
399
+ *
400
+ * Match only the exact request id. A late session event from a superseded turn
401
+ * must not mark its replacement (whose prompts have not reached the provider).
402
+ */
403
+ export function markRuntimeUserTurnProviderSessionObserved(
404
+ topicId: string,
405
+ requestId: string,
406
+ sessionId: string,
407
+ ): boolean {
408
+ return db
409
+ .transaction(() => {
410
+ const row = db
411
+ .query<RuntimeUserTurnRequestRow, [string, string]>(
412
+ "SELECT * FROM runtime_user_turn_requests WHERE topic_id = ? AND request_id = ?",
413
+ )
414
+ .get(topicId, requestId);
415
+ if (!row) return false;
416
+ const request = rowToRequest(row);
417
+ const execution: RuntimeUserTurnExecution = {
418
+ ...request.execution,
419
+ providerSessionId: sessionId,
420
+ };
421
+ const result = db
422
+ .query(
423
+ `UPDATE runtime_user_turn_requests
424
+ SET execution_json = ?
425
+ WHERE topic_id = ? AND request_id = ?`,
426
+ )
427
+ .run(JSON.stringify(execution), topicId, requestId);
428
+ return result.changes === 1;
429
+ })
430
+ .immediate();
431
+ }
432
+
374
433
  export function markRuntimeUserTurnMessagesLogged(
375
434
  topicId: string,
376
435
  requestId: string,
@@ -392,6 +451,8 @@ export function markRuntimeUserTurnMessagesLogged(
392
451
  const candidate = request.userMessages[index];
393
452
  return (
394
453
  candidate?.prompt === message.prompt &&
454
+ candidate.actorUserId === message.actorUserId &&
455
+ candidate.actorLabel === message.actorLabel &&
395
456
  JSON.stringify(candidate.attachments ?? []) ===
396
457
  JSON.stringify(message.attachments ?? [])
397
458
  );
@@ -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({
@@ -176,6 +176,8 @@ export interface AgentQueryOptions {
176
176
  cwd: string;
177
177
  systemPrompt: string;
178
178
  userId?: string;
179
+ /** Credential namespace when it differs from the execution principal. */
180
+ vaultUserId?: string;
179
181
  session?: string;
180
182
  playwrightPort?: number;
181
183
  playwrightCapability?: string;
@@ -1 +1 @@
1
- export const NEGOTIUM_VERSION = "0.2.3";
1
+ export const NEGOTIUM_VERSION = "0.2.5";
@@ -447,7 +447,6 @@ var BROWSER_DIR = resolveLocalStateDir("NEGOTIUM_BROWSER_DIR", "browser");
447
447
  var BROWSER_PROFILES_DIR = resolve(BROWSER_DIR, "profiles");
448
448
  var BINARIES_DIR = resolve(STATE_DIR, "binaries");
449
449
  var SECRETS_DIR = resolve(STATE_DIR, "secrets");
450
- var CONTEXTS_DIR = resolve(STATE_DIR, "data", "contexts");
451
450
  var DM_WORKSPACE_DIR = resolve(STATE_DIR, "data", "dm");
452
451
  var SESSION_WORKSPACE_DIR = resolve(STATE_DIR, "data", "sessions");
453
452
  var CLAUDE_EXECUTABLE_ENV = envText("NEGOTIUM_CLAUDE_EXECUTABLE");
@@ -590,7 +589,6 @@ mkdirSync2(WORKSPACE_DIR, { recursive: true });
590
589
  mkdirSync2(TOPIC_WORKSPACE_DIR, { recursive: true });
591
590
  mkdirSync2(SHARED_WIKI_DIR, { recursive: true });
592
591
  mkdirSync2(CRON_WORKSPACE_DIR, { recursive: true });
593
- mkdirSync2(CONTEXTS_DIR, { recursive: true });
594
592
  mkdirSync2(DM_WORKSPACE_DIR, { recursive: true });
595
593
  mkdirSync2(SESSION_WORKSPACE_DIR, { recursive: true });
596
594
  mkdirSync2(BROWSER_DIR, { recursive: true });
@@ -695,9 +693,29 @@ function defaultDataDir() {
695
693
  function defaultSessionsDatabasePath() {
696
694
  return envPath("SESSIONS_DB_PATH", join2(resolveStorageDataDir(), "sessions.db"));
697
695
  }
696
+ var SQLITE_INIT_RETRY_MS = 25;
697
+ var SQLITE_INIT_TIMEOUT_MS = 5000;
698
+ var SQLITE_INIT_SLEEP = new Int32Array(new SharedArrayBuffer(Int32Array.BYTES_PER_ELEMENT));
699
+ function isSqliteBusy(error) {
700
+ const message = error instanceof Error ? error.message : String(error);
701
+ return /database is (?:locked|busy)|SQLITE_(?:BUSY|LOCKED)/i.test(message);
702
+ }
703
+ function execWithBusyRetry(database, sql, timeoutMs = SQLITE_INIT_TIMEOUT_MS) {
704
+ const deadline = Date.now() + timeoutMs;
705
+ while (true) {
706
+ try {
707
+ database.exec(sql);
708
+ return;
709
+ } catch (error) {
710
+ if (!isSqliteBusy(error) || Date.now() >= deadline)
711
+ throw error;
712
+ Atomics.wait(SQLITE_INIT_SLEEP, 0, 0, Math.min(SQLITE_INIT_RETRY_MS, deadline - Date.now()));
713
+ }
714
+ }
715
+ }
698
716
  function initializeDatabase(database) {
699
717
  database.exec("PRAGMA busy_timeout = 5000");
700
- database.exec("PRAGMA journal_mode = WAL");
718
+ execWithBusyRetry(database, "PRAGMA journal_mode = WAL");
701
719
  database.exec("PRAGMA foreign_keys = ON");
702
720
  database.exec("PRAGMA wal_autocheckpoint = 1000");
703
721
  try {
@@ -1114,4 +1132,4 @@ export {
1114
1132
  CLAUDE_EFFORT_VALUES
1115
1133
  };
1116
1134
 
1117
- //# debugId=0849C2E75E1422A764756E2164756E21
1135
+ //# debugId=38CF47A0C3C063A564756E2164756E21