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.
- package/bin/negotium.mjs +0 -0
- package/dist/agent-helpers.js +601 -42
- package/dist/agent-helpers.js.map +8 -6
- package/dist/{chunk-6c637pwe.js → chunk-0jtan6fd.js} +3 -3
- package/dist/{chunk-6c637pwe.js.map → chunk-0jtan6fd.js.map} +2 -2
- package/dist/{chunk-kn8y5scr.js → chunk-rwj7x5pg.js} +4 -1
- package/dist/{chunk-kn8y5scr.js.map → chunk-rwj7x5pg.js.map} +3 -3
- package/dist/{chunk-bn1tt2qw.js → chunk-y4tyc603.js} +1 -1
- package/dist/hosted-agent.js +5 -2
- package/dist/hosted-agent.js.map +4 -4
- package/dist/main.js +1138 -252
- package/dist/main.js.map +21 -19
- package/dist/mcp-factories.js +620 -61
- package/dist/mcp-factories.js.map +8 -6
- package/dist/registry.js +3 -3
- package/dist/rollout.js +2 -2
- package/dist/runtime/src/agents/mcp-tools/request-vault.ts +616 -0
- package/dist/runtime/src/application/topic-service.ts +7 -0
- package/dist/runtime/src/index.ts +9 -0
- package/dist/runtime/src/mcp-runtime-host.ts +1 -0
- package/dist/runtime/src/node-host.ts +5 -0
- package/dist/runtime/src/runtime/turn-event-stream.ts +2 -0
- package/dist/runtime/src/storage/api-messages.ts +8 -2
- package/dist/runtime/src/storage/vault-request-gates.ts +359 -0
- package/dist/runtime/src/types/api.ts +23 -1
- package/dist/runtime/src/version.ts +1 -1
- package/dist/runtime-helpers.js +4 -1
- package/dist/runtime-helpers.js.map +3 -3
- package/dist/storage.js +8 -4
- package/dist/storage.js.map +3 -3
- package/dist/types/packages/core/src/agents/mcp-tools/request-vault.d.ts +120 -0
- package/dist/types/packages/core/src/storage/api-messages.d.ts +1 -0
- package/dist/types/packages/core/src/storage/vault-request-gates.d.ts +69 -0
- package/dist/types/packages/core/src/types/api.d.ts +15 -1
- package/dist/types/packages/core/src/version.d.ts +1 -1
- package/package.json +1 -1
- /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?:
|
|
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.
|
|
1
|
+
export const NEGOTIUM_VERSION = "0.22.4";
|
package/dist/runtime-helpers.js
CHANGED
|
@@ -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=
|
|
1793
|
+
//# debugId=BA53E29F7FBF701D64756E2164756E21
|