negotium 0.22.3 → 0.22.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 (37) hide show
  1. package/bin/negotium.mjs +0 -0
  2. package/dist/agent-helpers.js +601 -42
  3. package/dist/agent-helpers.js.map +8 -6
  4. package/dist/{chunk-6c637pwe.js → chunk-0jtan6fd.js} +3 -3
  5. package/dist/{chunk-6c637pwe.js.map → chunk-0jtan6fd.js.map} +2 -2
  6. package/dist/{chunk-kn8y5scr.js → chunk-rwj7x5pg.js} +4 -1
  7. package/dist/{chunk-kn8y5scr.js.map → chunk-rwj7x5pg.js.map} +3 -3
  8. package/dist/{chunk-bn1tt2qw.js → chunk-y4tyc603.js} +1 -1
  9. package/dist/hosted-agent.js +5 -2
  10. package/dist/hosted-agent.js.map +4 -4
  11. package/dist/main.js +1138 -252
  12. package/dist/main.js.map +21 -19
  13. package/dist/mcp-factories.js +620 -61
  14. package/dist/mcp-factories.js.map +8 -6
  15. package/dist/registry.js +3 -3
  16. package/dist/rollout.js +2 -2
  17. package/dist/runtime/src/agents/mcp-tools/request-vault.ts +616 -0
  18. package/dist/runtime/src/application/topic-service.ts +7 -0
  19. package/dist/runtime/src/index.ts +9 -0
  20. package/dist/runtime/src/mcp-runtime-host.ts +1 -0
  21. package/dist/runtime/src/node-host.ts +5 -0
  22. package/dist/runtime/src/runtime/turn-event-stream.ts +2 -0
  23. package/dist/runtime/src/storage/api-messages.ts +8 -2
  24. package/dist/runtime/src/storage/vault-request-gates.ts +359 -0
  25. package/dist/runtime/src/types/api.ts +23 -1
  26. package/dist/runtime/src/version.ts +1 -1
  27. package/dist/runtime-helpers.js +4 -1
  28. package/dist/runtime-helpers.js.map +3 -3
  29. package/dist/storage.js +8 -4
  30. package/dist/storage.js.map +3 -3
  31. package/dist/types/packages/core/src/agents/mcp-tools/request-vault.d.ts +120 -0
  32. package/dist/types/packages/core/src/storage/api-messages.d.ts +1 -0
  33. package/dist/types/packages/core/src/storage/vault-request-gates.d.ts +69 -0
  34. package/dist/types/packages/core/src/types/api.d.ts +15 -1
  35. package/dist/types/packages/core/src/version.d.ts +1 -1
  36. package/package.json +1 -1
  37. /package/dist/{chunk-bn1tt2qw.js.map → chunk-y4tyc603.js.map} +0 -0
@@ -2,6 +2,7 @@
2
2
  export { createAskUserToolDefinition } from "#agents/mcp-tools/ask-user";
3
3
  export { errorResult, textResult } from "#agents/mcp-tools/common";
4
4
  export { createPublishHtmlToolDefinitions } from "#agents/mcp-tools/publish-html";
5
+ export { createRequestVaultToolDefinition } from "#agents/mcp-tools/request-vault";
5
6
  export type { SelfConfigContext } from "#agents/mcp-tools/self-config";
6
7
  export { createSelfConfigToolDefinitions } from "#agents/mcp-tools/self-config";
7
8
  export {
@@ -5,6 +5,11 @@ export {
5
5
  startAskUserQuestionGateOwner,
6
6
  stopAskUserQuestionGateOwner,
7
7
  } from "#agents/mcp-tools/ask-user";
8
+ export {
9
+ reconcilePendingRequestVaultGates,
10
+ startRequestVaultGateOwner,
11
+ stopRequestVaultGateOwner,
12
+ } from "#agents/mcp-tools/request-vault";
8
13
  export { sweepStaleSubagentCards } from "#agents/mcp-tools/spawn-subagent";
9
14
  export {
10
15
  RuntimeGatewayIdempotencyConflictError,
@@ -9,6 +9,7 @@ import {
9
9
  scheduleIdleCompactForTopic,
10
10
  } from "#agents/idle-compact";
11
11
  import { cancelPendingAskUserQuestions } from "#agents/mcp-tools/ask-user";
12
+ import { cancelPendingVaultSecretRequests } from "#agents/mcp-tools/request-vault";
12
13
  import {
13
14
  settleSubagentFailure,
14
15
  settleSubagentSuccess,
@@ -935,6 +936,7 @@ export async function runTurnEventStream(
935
936
  const stillCurrent = getRoomQuery(roomId)?.queryId === queryId;
936
937
  clearRoomQuery(roomId, queryId);
937
938
  cancelPendingAskUserQuestions(topicId, queryId);
939
+ cancelPendingVaultSecretRequests(topicId, queryId);
938
940
  if (stillCurrent && roomId === topicId) clearQueryState(userId, topicId, topicTitle);
939
941
  // Stop typing only if this turn still owns the room. A superseded turn may
940
942
  // finish cleanup after its replacement already broadcast a fresh start.
@@ -80,6 +80,9 @@ function initializeApiMessagesSchema(): void {
80
80
  try {
81
81
  db.exec("ALTER TABLE api_messages ADD COLUMN subagent_card TEXT");
82
82
  } catch {}
83
+ try {
84
+ db.exec("ALTER TABLE api_messages ADD COLUMN vault_secret_request TEXT");
85
+ } catch {}
83
86
  // Index on topic_id; SQLite appends the implicit rowid to every index entry,
84
87
  // so `WHERE topic_id=? AND rowid>? ORDER BY rowid` stays index-driven.
85
88
  db.exec("CREATE INDEX IF NOT EXISTS idx_api_messages_topic ON api_messages(topic_id)");
@@ -111,6 +114,7 @@ export interface ApiMessageRow {
111
114
  ask_user_question: string | null;
112
115
  tell_card: string | null;
113
116
  subagent_card: string | null;
117
+ vault_secret_request: string | null;
114
118
  mentions: string | null;
115
119
  thread_root_id: string | null;
116
120
  created_at: string;
@@ -166,6 +170,7 @@ function rowToDto(r: ApiMessageRow): MessageDto {
166
170
  askUserQuestion: r.ask_user_question ? JSON.parse(r.ask_user_question) : undefined,
167
171
  tellCard: r.tell_card ? JSON.parse(r.tell_card) : undefined,
168
172
  subagentCard: r.subagent_card ? JSON.parse(r.subagent_card) : undefined,
173
+ vaultSecretRequest: r.vault_secret_request ? JSON.parse(r.vault_secret_request) : undefined,
169
174
  mentions: r.mentions ? JSON.parse(r.mentions) : undefined,
170
175
  threadRootId: r.thread_root_id ?? undefined,
171
176
  createdAt: r.created_at,
@@ -200,8 +205,8 @@ export function appendApiMessage(msg: MessageDto, options: AppendApiMessageOptio
200
205
  const result = db
201
206
  .query(
202
207
  `INSERT INTO api_messages
203
- (id, topic_id, parent_id, author_id, source_adapter, source_node, source_message_id, text, query_id, agent_type, model, attachments, usage, deleted, edited_at, reactions, kind, ask_user_question, tell_card, subagent_card, mentions, thread_root_id, created_at)
204
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
208
+ (id, topic_id, parent_id, author_id, source_adapter, source_node, source_message_id, text, query_id, agent_type, model, attachments, usage, deleted, edited_at, reactions, kind, ask_user_question, tell_card, subagent_card, vault_secret_request, mentions, thread_root_id, created_at)
209
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
205
210
  ON CONFLICT(id) DO NOTHING`,
206
211
  )
207
212
  .run(
@@ -225,6 +230,7 @@ export function appendApiMessage(msg: MessageDto, options: AppendApiMessageOptio
225
230
  msg.askUserQuestion ? JSON.stringify(msg.askUserQuestion) : null,
226
231
  msg.tellCard ? JSON.stringify(msg.tellCard) : null,
227
232
  msg.subagentCard ? JSON.stringify(msg.subagentCard) : null,
233
+ msg.vaultSecretRequest ? JSON.stringify(msg.vaultSecretRequest) : null,
228
234
  msg.mentions?.length ? JSON.stringify(msg.mentions) : null,
229
235
  msg.threadRootId ?? null,
230
236
  msg.createdAt,
@@ -0,0 +1,359 @@
1
+ import { db } from "#storage/forum-db";
2
+ import { registerStorageSchemaInitializer } from "#storage/storage-host";
3
+ import type { VaultSecretRequestDto } from "#types/api";
4
+
5
+ export type VaultRequestGateState =
6
+ | "pending"
7
+ | "claimed"
8
+ | "answered"
9
+ | "cancelled"
10
+ | "quarantined";
11
+
12
+ export interface VaultRequestGateRecord {
13
+ gateId: string;
14
+ topicId: string;
15
+ queryId?: string;
16
+ idempotencyKey: string;
17
+ bodyHash: string;
18
+ messageId: string;
19
+ ownerId: string;
20
+ state: VaultRequestGateState;
21
+ outcome?: "fulfilled" | "declined";
22
+ answeredBy?: string;
23
+ }
24
+
25
+ interface VaultRequestGateRow {
26
+ gate_id: string;
27
+ topic_id: string;
28
+ query_id: string | null;
29
+ idempotency_key: string;
30
+ body_hash: string;
31
+ message_id: string;
32
+ owner_id: string;
33
+ state: VaultRequestGateState;
34
+ outcome: "fulfilled" | "declined" | null;
35
+ answered_by: string | null;
36
+ }
37
+
38
+ export interface VaultRequestGateCardUpdate {
39
+ topicId: string;
40
+ messageId: string;
41
+ vaultSecretRequest: VaultSecretRequestDto;
42
+ editedAt: string;
43
+ }
44
+
45
+ function initializeVaultRequestGateSchema(): void {
46
+ db.exec(`
47
+ CREATE TABLE IF NOT EXISTS vault_request_gates (
48
+ gate_id TEXT PRIMARY KEY,
49
+ topic_id TEXT NOT NULL REFERENCES api_topics(id) ON DELETE CASCADE,
50
+ query_id TEXT,
51
+ idempotency_key TEXT NOT NULL,
52
+ body_hash TEXT NOT NULL,
53
+ message_id TEXT NOT NULL UNIQUE,
54
+ owner_id TEXT NOT NULL,
55
+ state TEXT NOT NULL,
56
+ outcome TEXT,
57
+ answered_by TEXT,
58
+ claim_source TEXT,
59
+ created_at TEXT NOT NULL,
60
+ updated_at TEXT NOT NULL
61
+ );
62
+ CREATE INDEX IF NOT EXISTS idx_vault_request_gates_idempotency
63
+ ON vault_request_gates(topic_id, idempotency_key, created_at);
64
+ CREATE UNIQUE INDEX IF NOT EXISTS idx_vault_request_gates_active_key
65
+ ON vault_request_gates(topic_id, idempotency_key)
66
+ WHERE state IN ('pending', 'claimed');
67
+ `);
68
+ }
69
+
70
+ registerStorageSchemaInitializer(initializeVaultRequestGateSchema, 32);
71
+
72
+ function fromRow(row: VaultRequestGateRow): VaultRequestGateRecord {
73
+ return {
74
+ gateId: row.gate_id,
75
+ topicId: row.topic_id,
76
+ queryId: row.query_id ?? undefined,
77
+ idempotencyKey: row.idempotency_key,
78
+ bodyHash: row.body_hash,
79
+ messageId: row.message_id,
80
+ ownerId: row.owner_id,
81
+ state: row.state,
82
+ outcome: row.outcome ?? undefined,
83
+ answeredBy: row.answered_by ?? undefined,
84
+ };
85
+ }
86
+
87
+ function latestGate(topicId: string, idempotencyKey: string): VaultRequestGateRecord | null {
88
+ const row = db
89
+ .query(
90
+ `SELECT * FROM vault_request_gates
91
+ WHERE topic_id = ? AND idempotency_key = ?
92
+ ORDER BY rowid DESC LIMIT 1`,
93
+ )
94
+ .get(topicId, idempotencyKey) as VaultRequestGateRow | undefined;
95
+ return row ? fromRow(row) : null;
96
+ }
97
+
98
+ function expireVaultCard(
99
+ topicId: string,
100
+ messageId: string,
101
+ editedAt: string,
102
+ ): VaultRequestGateCardUpdate | null {
103
+ const row = db
104
+ .query(
105
+ `SELECT vault_secret_request FROM api_messages
106
+ WHERE topic_id = ? AND id = ? AND deleted = 0 AND kind = 'vault_secret_request'`,
107
+ )
108
+ .get(topicId, messageId) as { vault_secret_request: string | null } | undefined;
109
+ if (!row?.vault_secret_request) return null;
110
+ const request = JSON.parse(row.vault_secret_request) as VaultSecretRequestDto;
111
+ if (request.status !== "pending") return null;
112
+ const expired: VaultSecretRequestDto = { ...request, status: "expired" };
113
+ db.query(
114
+ `UPDATE api_messages SET vault_secret_request = ?, edited_at = ?
115
+ WHERE topic_id = ? AND id = ? AND deleted = 0 AND kind = 'vault_secret_request'`,
116
+ ).run(JSON.stringify(expired), editedAt, topicId, messageId);
117
+ return { topicId, messageId, vaultSecretRequest: expired, editedAt };
118
+ }
119
+
120
+ export type PrepareVaultRequestGateResult =
121
+ | { outcome: "created"; gate: VaultRequestGateRecord }
122
+ | { outcome: "pending"; gate: VaultRequestGateRecord }
123
+ | { outcome: "replay"; gate: VaultRequestGateRecord }
124
+ | { outcome: "conflict"; gate: VaultRequestGateRecord };
125
+
126
+ export function prepareVaultRequestGate(args: {
127
+ gateId: string;
128
+ topicId: string;
129
+ queryId?: string;
130
+ idempotencyKey: string;
131
+ bodyHash: string;
132
+ messageId: string;
133
+ ownerId: string;
134
+ now: string;
135
+ }): PrepareVaultRequestGateResult {
136
+ return db
137
+ .transaction(() => {
138
+ const latest = latestGate(args.topicId, args.idempotencyKey);
139
+ if (latest?.bodyHash !== undefined && latest.bodyHash !== args.bodyHash) {
140
+ return { outcome: "conflict", gate: latest } as const;
141
+ }
142
+ if (latest?.state === "answered") return { outcome: "replay", gate: latest } as const;
143
+ if (latest && (latest.state === "pending" || latest.state === "claimed")) {
144
+ return { outcome: "pending", gate: latest } as const;
145
+ }
146
+
147
+ db.query(
148
+ `INSERT INTO vault_request_gates
149
+ (gate_id, topic_id, query_id, idempotency_key, body_hash, message_id, owner_id, state, created_at, updated_at)
150
+ VALUES (?, ?, ?, ?, ?, ?, ?, 'pending', ?, ?)`,
151
+ ).run(
152
+ args.gateId,
153
+ args.topicId,
154
+ args.queryId ?? null,
155
+ args.idempotencyKey,
156
+ args.bodyHash,
157
+ args.messageId,
158
+ args.ownerId,
159
+ args.now,
160
+ args.now,
161
+ );
162
+ return {
163
+ outcome: "created",
164
+ gate: {
165
+ gateId: args.gateId,
166
+ topicId: args.topicId,
167
+ queryId: args.queryId,
168
+ idempotencyKey: args.idempotencyKey,
169
+ bodyHash: args.bodyHash,
170
+ messageId: args.messageId,
171
+ ownerId: args.ownerId,
172
+ state: "pending",
173
+ },
174
+ } as const;
175
+ })
176
+ .immediate();
177
+ }
178
+
179
+ export function quarantineVaultRequestGate(
180
+ gateId: string,
181
+ ownerId: string,
182
+ now = new Date().toISOString(),
183
+ ): VaultRequestGateCardUpdate | null {
184
+ return db
185
+ .transaction(() => {
186
+ const row = db.query("SELECT * FROM vault_request_gates WHERE gate_id = ?").get(gateId) as
187
+ | VaultRequestGateRow
188
+ | undefined;
189
+ if (!row || row.owner_id !== ownerId || !["pending", "claimed"].includes(row.state)) {
190
+ return null;
191
+ }
192
+ const result = db
193
+ .query(
194
+ `UPDATE vault_request_gates SET state = 'quarantined', updated_at = ?
195
+ WHERE gate_id = ? AND owner_id = ? AND state IN ('pending', 'claimed')`,
196
+ )
197
+ .run(now, gateId, ownerId);
198
+ if (Number(result.changes ?? 0) === 0) return null;
199
+ return expireVaultCard(row.topic_id, row.message_id, now);
200
+ })
201
+ .immediate();
202
+ }
203
+
204
+ export function quarantineForeignVaultRequestGates(
205
+ liveOwnerIds: ReadonlySet<string>,
206
+ now = new Date().toISOString(),
207
+ ): VaultRequestGateCardUpdate[] {
208
+ return db
209
+ .transaction(() => {
210
+ const rows = db
211
+ .query(
212
+ `SELECT * FROM vault_request_gates
213
+ WHERE state IN ('pending', 'claimed')`,
214
+ )
215
+ .all() as VaultRequestGateRow[];
216
+ const updates: VaultRequestGateCardUpdate[] = [];
217
+ for (const row of rows) {
218
+ if (liveOwnerIds.has(row.owner_id)) continue;
219
+ if (row.state === "claimed" && row.outcome && row.answered_by) {
220
+ db.query(
221
+ `UPDATE vault_request_gates SET state = 'answered', updated_at = ?
222
+ WHERE gate_id = ? AND state = 'claimed'`,
223
+ ).run(now, row.gate_id);
224
+ continue;
225
+ }
226
+ const result = db
227
+ .query(
228
+ `UPDATE vault_request_gates SET state = 'quarantined', updated_at = ?
229
+ WHERE gate_id = ? AND state IN ('pending', 'claimed')`,
230
+ )
231
+ .run(now, row.gate_id);
232
+ if (Number(result.changes ?? 0) === 0) continue;
233
+ const update = expireVaultCard(row.topic_id, row.message_id, now);
234
+ if (update) updates.push(update);
235
+ }
236
+ const legacyCards = db
237
+ .query(
238
+ `SELECT m.topic_id, m.id
239
+ FROM api_messages m
240
+ LEFT JOIN vault_request_gates g ON g.message_id = m.id
241
+ WHERE m.kind = 'vault_secret_request'
242
+ AND m.deleted = 0
243
+ AND g.gate_id IS NULL`,
244
+ )
245
+ .all() as { topic_id: string; id: string }[];
246
+ for (const card of legacyCards) {
247
+ const update = expireVaultCard(card.topic_id, card.id, now);
248
+ if (update) updates.push(update);
249
+ }
250
+ return updates;
251
+ })
252
+ .immediate();
253
+ }
254
+
255
+ export type ClaimVaultRequestGateResult =
256
+ | {
257
+ outcome: "claimed";
258
+ gate: VaultRequestGateRecord;
259
+ vaultSecretRequest: VaultSecretRequestDto;
260
+ editedAt: string;
261
+ }
262
+ | { outcome: "unavailable" };
263
+
264
+ /**
265
+ * Claim the gate and mark the card's outcome. This never touches the secret
266
+ * value itself — the caller is responsible for writing to the vault (via
267
+ * `vaultSet`) *before* calling this, so the card only ever reflects
268
+ * "fulfilled"/"declined", never the plaintext.
269
+ */
270
+ export function claimVaultRequestGateAndAnswer(args: {
271
+ topicId: string;
272
+ messageId: string;
273
+ outcome: "fulfilled" | "declined";
274
+ userId: string;
275
+ ownerId: string;
276
+ source: string;
277
+ now: string;
278
+ }): ClaimVaultRequestGateResult {
279
+ return db
280
+ .transaction(() => {
281
+ const row = db
282
+ .query(
283
+ `SELECT * FROM vault_request_gates
284
+ WHERE topic_id = ? AND message_id = ?
285
+ ORDER BY rowid DESC LIMIT 1`,
286
+ )
287
+ .get(args.topicId, args.messageId) as VaultRequestGateRow | undefined;
288
+ if (!row || row.owner_id !== args.ownerId || row.state !== "pending") {
289
+ return { outcome: "unavailable" } as const;
290
+ }
291
+ const message = db
292
+ .query(
293
+ `SELECT vault_secret_request FROM api_messages
294
+ WHERE topic_id = ? AND id = ? AND deleted = 0 AND kind = 'vault_secret_request'`,
295
+ )
296
+ .get(args.topicId, args.messageId) as { vault_secret_request: string | null } | undefined;
297
+ if (!message?.vault_secret_request) return { outcome: "unavailable" } as const;
298
+ const request = JSON.parse(message.vault_secret_request) as VaultSecretRequestDto;
299
+ if (request.status !== "pending") return { outcome: "unavailable" } as const;
300
+
301
+ const claimed = db
302
+ .query(
303
+ `UPDATE vault_request_gates
304
+ SET state = 'answered', outcome = ?, answered_by = ?, claim_source = ?, updated_at = ?
305
+ WHERE gate_id = ? AND owner_id = ? AND state = 'pending'`,
306
+ )
307
+ .run(args.outcome, args.userId, args.source, args.now, row.gate_id, args.ownerId);
308
+ if (Number(claimed.changes ?? 0) === 0) return { outcome: "unavailable" } as const;
309
+
310
+ const answered: VaultSecretRequestDto = {
311
+ ...request,
312
+ status: args.outcome,
313
+ answeredBy: args.userId,
314
+ };
315
+ db.query(
316
+ `UPDATE api_messages SET vault_secret_request = ?, edited_at = ?
317
+ WHERE topic_id = ? AND id = ? AND deleted = 0 AND kind = 'vault_secret_request'`,
318
+ ).run(JSON.stringify(answered), args.now, args.topicId, args.messageId);
319
+ return {
320
+ outcome: "claimed",
321
+ gate: fromRow({
322
+ ...row,
323
+ state: "answered",
324
+ outcome: args.outcome,
325
+ answered_by: args.userId,
326
+ }),
327
+ vaultSecretRequest: answered,
328
+ editedAt: args.now,
329
+ } as const;
330
+ })
331
+ .immediate();
332
+ }
333
+
334
+ export function cancelVaultRequestGate(
335
+ topicId: string,
336
+ messageId: string,
337
+ ownerId: string,
338
+ now = new Date().toISOString(),
339
+ ): VaultRequestGateCardUpdate | null {
340
+ return db
341
+ .transaction(() => {
342
+ const row = db
343
+ .query(
344
+ `SELECT * FROM vault_request_gates
345
+ WHERE topic_id = ? AND message_id = ? ORDER BY rowid DESC LIMIT 1`,
346
+ )
347
+ .get(topicId, messageId) as VaultRequestGateRow | undefined;
348
+ if (!row || row.owner_id !== ownerId || row.state !== "pending") return null;
349
+ const result = db
350
+ .query(
351
+ `UPDATE vault_request_gates SET state = 'cancelled', updated_at = ?
352
+ WHERE gate_id = ? AND owner_id = ? AND state = 'pending'`,
353
+ )
354
+ .run(now, row.gate_id, ownerId);
355
+ if (Number(result.changes ?? 0) === 0) return null;
356
+ return expireVaultCard(topicId, messageId, now);
357
+ })
358
+ .immediate();
359
+ }
@@ -263,8 +263,16 @@ export interface MessageDto {
263
263
  threadLastReplyAt?: string;
264
264
  /** Emoji reactions, one entry per (user, emoji). */
265
265
  reactions?: ReactionDto[];
266
- kind?: "message" | "system" | "tool" | "ask_user_question" | "subagent" | "tell";
266
+ kind?:
267
+ | "message"
268
+ | "system"
269
+ | "tool"
270
+ | "ask_user_question"
271
+ | "vault_secret_request"
272
+ | "subagent"
273
+ | "tell";
267
274
  askUserQuestion?: AskUserQuestionDto;
275
+ vaultSecretRequest?: VaultSecretRequestDto;
268
276
  subagentCard?: SubagentCardDto;
269
277
  tellCard?: TellCardDto;
270
278
  createdAt: string;
@@ -276,6 +284,19 @@ export interface AskUserQuestionDto {
276
284
  expired?: boolean;
277
285
  }
278
286
 
287
+ /**
288
+ * Blocking card for `request_vault_secret`: the agent proposes a key and
289
+ * description, a person types only the value on a surface that can mask
290
+ * input, and the plaintext never flows back through this DTO, a tool
291
+ * result, or chat history — only the outcome does.
292
+ */
293
+ export interface VaultSecretRequestDto {
294
+ key: string;
295
+ description?: string;
296
+ status: "pending" | "fulfilled" | "declined" | "expired";
297
+ answeredBy?: string;
298
+ }
299
+
279
300
  /** Structured cross-session delivery. Never inferred from system-message text. */
280
301
  export interface TellCardDto {
281
302
  fromLabel: string;
@@ -485,6 +506,7 @@ export type WsServerMessage =
485
506
  editedAt?: string;
486
507
  usage?: MessageTokenUsage;
487
508
  askUserQuestion?: AskUserQuestionDto;
509
+ vaultSecretRequest?: VaultSecretRequestDto;
488
510
  subagentCard?: SubagentCardDto;
489
511
  }
490
512
  | {
@@ -1 +1 @@
1
- export const NEGOTIUM_VERSION = "0.22.3";
1
+ export const NEGOTIUM_VERSION = "0.22.4";
@@ -1424,6 +1424,9 @@ function initializeApiMessagesSchema() {
1424
1424
  try {
1425
1425
  db.exec("ALTER TABLE api_messages ADD COLUMN subagent_card TEXT");
1426
1426
  } catch {}
1427
+ try {
1428
+ db.exec("ALTER TABLE api_messages ADD COLUMN vault_secret_request TEXT");
1429
+ } catch {}
1427
1430
  db.exec("CREATE INDEX IF NOT EXISTS idx_api_messages_topic ON api_messages(topic_id)");
1428
1431
  db.exec("CREATE INDEX IF NOT EXISTS idx_api_messages_thread_root ON api_messages(thread_root_id)");
1429
1432
  }
@@ -1787,4 +1790,4 @@ export {
1787
1790
  CLAUDE_EFFORT_VALUES
1788
1791
  };
1789
1792
 
1790
- //# debugId=8F828420EBB035F564756E2164756E21
1793
+ //# debugId=BA53E29F7FBF701D64756E2164756E21