negotium 0.2.4 → 0.2.6

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 (62) hide show
  1. package/dist/agent-helpers.js +241 -127
  2. package/dist/agent-helpers.js.map +19 -19
  3. package/dist/background-bash.js +1 -3
  4. package/dist/background-bash.js.map +4 -4
  5. package/dist/browser-runtime.js +1 -3
  6. package/dist/browser-runtime.js.map +4 -4
  7. package/dist/{chunk-1s9ryz8g.js → chunk-qmbcbhyy.js} +20 -12
  8. package/dist/chunk-qmbcbhyy.js.map +35 -0
  9. package/dist/hosted-agent.js +13 -10
  10. package/dist/hosted-agent.js.map +10 -10
  11. package/dist/main.js +607 -438
  12. package/dist/main.js.map +30 -29
  13. package/dist/mcp-factories.js +238 -123
  14. package/dist/mcp-factories.js.map +18 -18
  15. package/dist/prompts.js +1 -3
  16. package/dist/prompts.js.map +4 -4
  17. package/dist/query-runtime.js +1 -3
  18. package/dist/query-runtime.js.map +5 -5
  19. package/dist/registry.js +3 -3
  20. package/dist/registry.js.map +2 -2
  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-provider.ts +1 -0
  25. package/dist/runtime/src/agents/index.ts +19 -14
  26. package/dist/runtime/src/agents/maestro-provider.ts +1 -1
  27. package/dist/runtime/src/agents/rollout/codex.ts +25 -18
  28. package/dist/runtime/src/agents/self-config-core.ts +2 -4
  29. package/dist/runtime/src/agents/topic-agent-switch.ts +4 -10
  30. package/dist/runtime/src/application/submit-runtime-gateway-turn.ts +91 -19
  31. package/dist/runtime/src/application/switch-topic-model.ts +7 -10
  32. package/dist/runtime/src/index.ts +4 -0
  33. package/dist/runtime/src/mcp/wiki-server.ts +19 -12
  34. package/dist/runtime/src/platform/config.ts +0 -2
  35. package/dist/runtime/src/platform/log-rotation.ts +45 -0
  36. package/dist/runtime/src/platform/mcp-config.ts +9 -3
  37. package/dist/runtime/src/query/active-rooms.ts +7 -1
  38. package/dist/runtime/src/runtime/errors.ts +1 -0
  39. package/dist/runtime/src/runtime/turn-event-stream.ts +46 -1
  40. package/dist/runtime/src/runtime/turn-runner.ts +176 -100
  41. package/dist/runtime/src/runtime/user-turn-envelope.ts +19 -0
  42. package/dist/runtime/src/storage/runtime-gateway-submissions.ts +32 -3
  43. package/dist/runtime/src/storage/runtime-turn-requests.ts +68 -7
  44. package/dist/runtime/src/types.ts +2 -0
  45. package/dist/runtime/src/version.ts +1 -1
  46. package/dist/runtime-helpers.js +1 -3
  47. package/dist/runtime-helpers.js.map +4 -4
  48. package/dist/storage.js.map +1 -1
  49. package/dist/types/packages/core/src/agents/api-topic-agent-switch.d.ts +1 -1
  50. package/dist/types/packages/core/src/agents/self-config-core.d.ts +1 -1
  51. package/dist/types/packages/core/src/platform/config.d.ts +0 -1
  52. package/dist/types/packages/core/src/platform/mcp-config.d.ts +3 -0
  53. package/dist/types/packages/core/src/query/active-rooms.d.ts +7 -1
  54. package/dist/types/packages/core/src/runtime/turn-runner.d.ts +42 -1
  55. package/dist/types/packages/core/src/runtime/user-turn-envelope.d.ts +7 -0
  56. package/dist/types/packages/core/src/storage/runtime-turn-requests.d.ts +17 -0
  57. package/dist/types/packages/core/src/types.d.ts +2 -0
  58. package/dist/types/packages/core/src/version.d.ts +1 -1
  59. package/dist/vault.js +1 -3
  60. package/dist/vault.js.map +4 -4
  61. package/package.json +1 -1
  62. package/dist/chunk-1s9ryz8g.js.map +0 -35
@@ -9,6 +9,8 @@ export interface RuntimeGatewaySubmission {
9
9
  createdAt: string;
10
10
  ackCursor: number;
11
11
  messageCursor: number;
12
+ /** Hash of every ingress field that can change canonical or execution semantics. */
13
+ payloadHash?: string;
12
14
  }
13
15
 
14
16
  interface RuntimeGatewaySubmissionRow {
@@ -20,6 +22,7 @@ interface RuntimeGatewaySubmissionRow {
20
22
  created_at: string;
21
23
  ack_cursor: number | bigint;
22
24
  message_cursor: number | bigint;
25
+ payload_hash: string | null;
23
26
  }
24
27
 
25
28
  db.exec(`
@@ -31,7 +34,8 @@ db.exec(`
31
34
  user_id TEXT NOT NULL,
32
35
  created_at TEXT NOT NULL,
33
36
  ack_cursor INTEGER NOT NULL DEFAULT 0,
34
- message_cursor INTEGER NOT NULL DEFAULT 0
37
+ message_cursor INTEGER NOT NULL DEFAULT 0,
38
+ payload_hash TEXT
35
39
  )
36
40
  `);
37
41
  try {
@@ -39,6 +43,9 @@ try {
39
43
  "ALTER TABLE runtime_gateway_submissions ADD COLUMN ack_cursor INTEGER NOT NULL DEFAULT 0",
40
44
  );
41
45
  } catch {}
46
+ try {
47
+ db.exec("ALTER TABLE runtime_gateway_submissions ADD COLUMN payload_hash TEXT");
48
+ } catch {}
42
49
  try {
43
50
  db.exec(
44
51
  "ALTER TABLE runtime_gateway_submissions ADD COLUMN message_cursor INTEGER NOT NULL DEFAULT 0",
@@ -58,6 +65,7 @@ function rowToSubmission(row: RuntimeGatewaySubmissionRow): RuntimeGatewaySubmis
58
65
  createdAt: row.created_at,
59
66
  ackCursor: Number(row.ack_cursor),
60
67
  messageCursor: Number(row.message_cursor),
68
+ payloadHash: row.payload_hash ?? undefined,
61
69
  };
62
70
  }
63
71
 
@@ -76,12 +84,32 @@ export function findRuntimeGatewaySubmission(
76
84
  return row ? rowToSubmission(row) : null;
77
85
  }
78
86
 
87
+ /**
88
+ * Attach a payload hash to a submission recorded before the `payload_hash`
89
+ * column existed (or before it was populated for that row). Called the first
90
+ * time a legacy row is confirmed to match a duplicate request on every field
91
+ * we *can* verify, so every subsequent replay under the same key is checked
92
+ * against the full semantic payload instead of the narrower legacy field set.
93
+ * A no-op once the row already has a hash, so it never overwrites a value
94
+ * `submitRuntimeGatewayTurn` already trusts.
95
+ */
96
+ export function backfillRuntimeGatewaySubmissionPayloadHash(
97
+ clientMessageId: string,
98
+ payloadHash: string,
99
+ ): void {
100
+ db.query(
101
+ `UPDATE runtime_gateway_submissions
102
+ SET payload_hash = ?
103
+ WHERE client_message_id = ? AND payload_hash IS NULL`,
104
+ ).run(payloadHash, clientMessageId);
105
+ }
106
+
79
107
  export function recordRuntimeGatewaySubmission(submission: RuntimeGatewaySubmission): void {
80
108
  db.query(
81
109
  `INSERT INTO runtime_gateway_submissions
82
110
  (client_message_id, request_id, topic_id, message_id, user_id, created_at,
83
- ack_cursor, message_cursor)
84
- VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
111
+ ack_cursor, message_cursor, payload_hash)
112
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,
85
113
  ).run(
86
114
  submission.clientMessageId,
87
115
  submission.requestId,
@@ -91,5 +119,6 @@ export function recordRuntimeGatewaySubmission(submission: RuntimeGatewaySubmiss
91
119
  submission.createdAt,
92
120
  submission.ackCursor,
93
121
  submission.messageCursor,
122
+ submission.payloadHash ?? null,
94
123
  );
95
124
  }
@@ -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
  );
@@ -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.4";
1
+ export const NEGOTIUM_VERSION = "0.2.6";
@@ -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 });
@@ -1134,4 +1132,4 @@ export {
1134
1132
  CLAUDE_EFFORT_VALUES
1135
1133
  };
1136
1134
 
1137
- //# debugId=16168DE340D196C364756E2164756E21
1135
+ //# debugId=38CF47A0C3C063A564756E2164756E21