negotium 0.22.3 → 0.22.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.
- package/bin/negotium.mjs +0 -0
- package/dist/agent-helpers.js +615 -48
- package/dist/agent-helpers.js.map +11 -9
- package/dist/{chunk-6c637pwe.js → chunk-7rpbcn1j.js} +3 -3
- package/dist/{chunk-6c637pwe.js.map → chunk-7rpbcn1j.js.map} +2 -2
- package/dist/{chunk-bn1tt2qw.js → chunk-a3me2mje.js} +3 -3
- package/dist/chunk-a3me2mje.js.map +10 -0
- package/dist/{chunk-kn8y5scr.js → chunk-vh8px000.js} +12 -3
- package/dist/{chunk-kn8y5scr.js.map → chunk-vh8px000.js.map} +4 -4
- package/dist/hosted-agent.js +13 -4
- package/dist/hosted-agent.js.map +5 -5
- package/dist/main.js +1168 -264
- package/dist/main.js.map +25 -23
- package/dist/mcp-factories.js +634 -67
- package/dist/mcp-factories.js.map +11 -9
- 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/agents/rollout/claude.ts +6 -1
- package/dist/runtime/src/application/submit-runtime-gateway-turn.ts +6 -4
- 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 +6 -0
- package/dist/runtime/src/platform/mcp-config.ts +60 -28
- package/dist/runtime/src/runtime/turn-event-stream.ts +2 -0
- package/dist/runtime/src/runtime/turn-runner.ts +9 -4
- 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/agents/rollout/claude.d.ts +5 -0
- package/dist/types/packages/core/src/platform/mcp-config.d.ts +8 -0
- package/dist/types/packages/core/src/runtime/turn-runner.d.ts +6 -2
- 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 +0 -10
package/dist/agent-helpers.js
CHANGED
|
@@ -1807,6 +1807,9 @@ function initializeApiMessagesSchema() {
|
|
|
1807
1807
|
try {
|
|
1808
1808
|
db.exec("ALTER TABLE api_messages ADD COLUMN subagent_card TEXT");
|
|
1809
1809
|
} catch {}
|
|
1810
|
+
try {
|
|
1811
|
+
db.exec("ALTER TABLE api_messages ADD COLUMN vault_secret_request TEXT");
|
|
1812
|
+
} catch {}
|
|
1810
1813
|
db.exec("CREATE INDEX IF NOT EXISTS idx_api_messages_topic ON api_messages(topic_id)");
|
|
1811
1814
|
db.exec("CREATE INDEX IF NOT EXISTS idx_api_messages_thread_root ON api_messages(thread_root_id)");
|
|
1812
1815
|
}
|
|
@@ -1843,6 +1846,7 @@ function rowToDto(r) {
|
|
|
1843
1846
|
askUserQuestion: r.ask_user_question ? JSON.parse(r.ask_user_question) : undefined,
|
|
1844
1847
|
tellCard: r.tell_card ? JSON.parse(r.tell_card) : undefined,
|
|
1845
1848
|
subagentCard: r.subagent_card ? JSON.parse(r.subagent_card) : undefined,
|
|
1849
|
+
vaultSecretRequest: r.vault_secret_request ? JSON.parse(r.vault_secret_request) : undefined,
|
|
1846
1850
|
mentions: r.mentions ? JSON.parse(r.mentions) : undefined,
|
|
1847
1851
|
threadRootId: r.thread_root_id ?? undefined,
|
|
1848
1852
|
createdAt: r.created_at
|
|
@@ -1864,9 +1868,9 @@ function appendApiMessage(msg, options = {}) {
|
|
|
1864
1868
|
let inserted = false;
|
|
1865
1869
|
db.transaction(() => {
|
|
1866
1870
|
const result = db.query(`INSERT INTO api_messages
|
|
1867
|
-
(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)
|
|
1868
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
1869
|
-
ON CONFLICT(id) DO NOTHING`).run(msg.id, msg.topicId, msg.parentId ?? null, msg.authorId, msg.sourceAdapter ?? null, msg.sourceNode ?? null, msg.sourceMessageId ?? null, msg.text, msg.queryId ?? null, msg.agentType ?? null, msg.model ?? null, msg.attachments ? JSON.stringify(msg.attachments) : null, msg.usage ? JSON.stringify(msg.usage) : null, msg.deleted ? 1 : 0, msg.editedAt ?? null, msg.reactions?.length ? JSON.stringify(msg.reactions) : null, msg.kind ?? null, msg.askUserQuestion ? JSON.stringify(msg.askUserQuestion) : null, msg.tellCard ? JSON.stringify(msg.tellCard) : null, msg.subagentCard ? JSON.stringify(msg.subagentCard) : null, msg.mentions?.length ? JSON.stringify(msg.mentions) : null, msg.threadRootId ?? null, msg.createdAt);
|
|
1871
|
+
(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)
|
|
1872
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
1873
|
+
ON CONFLICT(id) DO NOTHING`).run(msg.id, msg.topicId, msg.parentId ?? null, msg.authorId, msg.sourceAdapter ?? null, msg.sourceNode ?? null, msg.sourceMessageId ?? null, msg.text, msg.queryId ?? null, msg.agentType ?? null, msg.model ?? null, msg.attachments ? JSON.stringify(msg.attachments) : null, msg.usage ? JSON.stringify(msg.usage) : null, msg.deleted ? 1 : 0, msg.editedAt ?? null, msg.reactions?.length ? JSON.stringify(msg.reactions) : null, msg.kind ?? null, msg.askUserQuestion ? JSON.stringify(msg.askUserQuestion) : null, msg.tellCard ? JSON.stringify(msg.tellCard) : null, msg.subagentCard ? JSON.stringify(msg.subagentCard) : null, msg.vaultSecretRequest ? JSON.stringify(msg.vaultSecretRequest) : null, msg.mentions?.length ? JSON.stringify(msg.mentions) : null, msg.threadRootId ?? null, msg.createdAt);
|
|
1870
1874
|
inserted = Number(result.changes ?? 0) > 0;
|
|
1871
1875
|
if (inserted && updateTopicLastMessageAt && !msg.deleted) {
|
|
1872
1876
|
db.query(`UPDATE api_topics
|
|
@@ -3240,6 +3244,11 @@ function refreshForumCatalogViews() {
|
|
|
3240
3244
|
function isReservedRuntimeMcpServerName(name) {
|
|
3241
3245
|
return Object.hasOwn(MCP_CATALOG, name) || nodeMcpEntries.some((entry) => entry.key === name);
|
|
3242
3246
|
}
|
|
3247
|
+
function withoutManagerOnlyHostMcpServers(hostMcpServers) {
|
|
3248
|
+
if (!hostMcpServers)
|
|
3249
|
+
return;
|
|
3250
|
+
return Object.fromEntries(Object.entries(hostMcpServers).filter(([name]) => !MANAGER_ONLY_HOST_MCP_NAMES.has(name)));
|
|
3251
|
+
}
|
|
3243
3252
|
function mergeHostMcpServers(base, hostMcpServers) {
|
|
3244
3253
|
if (!hostMcpServers)
|
|
3245
3254
|
return base;
|
|
@@ -3500,7 +3509,7 @@ function getMcpServersForQuery(opts) {
|
|
|
3500
3509
|
fileDeliveryTools: opts.fileDeliveryTools
|
|
3501
3510
|
}), opts.hostMcpServers), opts.topicId);
|
|
3502
3511
|
}
|
|
3503
|
-
return hideProviderIntrospectionForOtium(getForumMcpServers({
|
|
3512
|
+
return hideProviderIntrospectionForOtium(mergeHostMcpServers(getForumMcpServers({
|
|
3504
3513
|
userId: opts.userId || "local",
|
|
3505
3514
|
actorUserId: opts.actorUserId,
|
|
3506
3515
|
actorTopicScope: opts.actorTopicScope,
|
|
@@ -3527,7 +3536,7 @@ function getMcpServersForQuery(opts) {
|
|
|
3527
3536
|
extra: opts.mcpExtra,
|
|
3528
3537
|
silent: opts.silent,
|
|
3529
3538
|
peerBridge: opts.peerBridge
|
|
3530
|
-
}), opts.topicId);
|
|
3539
|
+
}), withoutManagerOnlyHostMcpServers(opts.hostMcpServers)), opts.topicId);
|
|
3531
3540
|
}
|
|
3532
3541
|
function hideProviderIntrospectionForOtium(servers, topicId) {
|
|
3533
3542
|
if (!topicId || getTopic(topicId)?.surface !== "otium")
|
|
@@ -3537,7 +3546,7 @@ function hideProviderIntrospectionForOtium(servers, topicId) {
|
|
|
3537
3546
|
delete filtered["token-stats"];
|
|
3538
3547
|
return filtered;
|
|
3539
3548
|
}
|
|
3540
|
-
var _playwrightUnavailableNotifier, _playwrightUnavailableLastNotifiedAt, _PLAYWRIGHT_UNAVAILABLE_COOLDOWN_MS, _playwrightUnavailableThisTurn, cuaRsMcpPort, cuaRsMcpToken, MCP_CATALOG, nodeMcpEntries, resolvedNodeMcpPorts, allForumMcpServerNames, requiredForumMcpServers, REQUIRED_FORUM_MCP_SERVERS, optionalForumMcpServers;
|
|
3549
|
+
var _playwrightUnavailableNotifier, _playwrightUnavailableLastNotifiedAt, _PLAYWRIGHT_UNAVAILABLE_COOLDOWN_MS, _playwrightUnavailableThisTurn, cuaRsMcpPort, cuaRsMcpToken, MCP_CATALOG, nodeMcpEntries, resolvedNodeMcpPorts, allForumMcpServerNames, requiredForumMcpServers, REQUIRED_FORUM_MCP_SERVERS, optionalForumMcpServers, MANAGER_ONLY_HOST_MCP_NAMES;
|
|
3541
3550
|
var init_mcp_config = __esm(async () => {
|
|
3542
3551
|
init_explicit_agent_switch();
|
|
3543
3552
|
init_canonical_bridge_config();
|
|
@@ -3793,6 +3802,7 @@ var init_mcp_config = __esm(async () => {
|
|
|
3793
3802
|
REQUIRED_FORUM_MCP_SERVERS = requiredForumMcpServers;
|
|
3794
3803
|
optionalForumMcpServers = [];
|
|
3795
3804
|
refreshForumCatalogViews();
|
|
3805
|
+
MANAGER_ONLY_HOST_MCP_NAMES = new Set(["topic-admin"]);
|
|
3796
3806
|
});
|
|
3797
3807
|
|
|
3798
3808
|
// ../../packages/core/src/storage/vault-crypto-core.ts
|
|
@@ -3926,6 +3936,16 @@ function vaultListDecryptableValues(userId) {
|
|
|
3926
3936
|
}
|
|
3927
3937
|
return entries;
|
|
3928
3938
|
}
|
|
3939
|
+
function vaultSet(userId, key, value, description = "") {
|
|
3940
|
+
const normalizedKey = normalizeVaultKey(key);
|
|
3941
|
+
activeVaultDatabase().prepare(`INSERT INTO vault (user_id, key, value, description) VALUES (?, ?, ?, ?)
|
|
3942
|
+
ON CONFLICT (user_id, key) DO UPDATE
|
|
3943
|
+
SET value = excluded.value, description = excluded.description`).run(userId, normalizedKey, encryptVaultValue(userId, normalizedKey, value, vaultMasterKey), description);
|
|
3944
|
+
}
|
|
3945
|
+
function vaultHasKey(userId, key) {
|
|
3946
|
+
const row = activeVaultDatabase().prepare("SELECT 1 FROM vault WHERE user_id = ? AND key = ?").get(userId, normalizeVaultKey(key));
|
|
3947
|
+
return row != null;
|
|
3948
|
+
}
|
|
3929
3949
|
function vaultGetValue(userId, key) {
|
|
3930
3950
|
const normalizedKey = normalizeVaultKey(key);
|
|
3931
3951
|
const row = activeVaultDatabase().prepare("SELECT key, value FROM vault WHERE user_id = ? AND key = ?").get(userId, normalizedKey);
|
|
@@ -3984,7 +4004,7 @@ function redactVaultSecrets(userId, text) {
|
|
|
3984
4004
|
}
|
|
3985
4005
|
return redacted;
|
|
3986
4006
|
}
|
|
3987
|
-
var vaultDb, vaultMasterKey, VAULT_VALUE_MAX_BYTES;
|
|
4007
|
+
var vaultDb, vaultMasterKey, VAULT_KEY_PATTERN, VAULT_VALUE_MIN_BYTES = 4, VAULT_VALUE_MAX_BYTES, VAULT_DESCRIPTION_MAX_LENGTH = 500;
|
|
3988
4008
|
var init_vault = __esm(async () => {
|
|
3989
4009
|
init_config();
|
|
3990
4010
|
init_vault_crypto();
|
|
@@ -3993,6 +4013,7 @@ var init_vault = __esm(async () => {
|
|
|
3993
4013
|
init_storage_host()
|
|
3994
4014
|
]);
|
|
3995
4015
|
vaultMasterKey = VAULT_MASTER_KEY;
|
|
4016
|
+
VAULT_KEY_PATTERN = /^[A-Z][A-Z0-9_]{0,127}$/;
|
|
3996
4017
|
VAULT_VALUE_MAX_BYTES = 64 * 1024;
|
|
3997
4018
|
});
|
|
3998
4019
|
|
|
@@ -4476,7 +4497,7 @@ function loadClaudeAttachments() {
|
|
|
4476
4497
|
}
|
|
4477
4498
|
function encodeClaudeCwd(cwd) {
|
|
4478
4499
|
const realCwd = cwd.startsWith("/tmp/") ? cwd.replace(/^\/tmp\//, "/private/tmp/") : cwd;
|
|
4479
|
-
return
|
|
4500
|
+
return realCwd.replaceAll(/[^a-zA-Z0-9]/g, "-");
|
|
4480
4501
|
}
|
|
4481
4502
|
function writeClaudeRollout(opts) {
|
|
4482
4503
|
const sessionId = opts.sessionId ?? randomUUID2();
|
|
@@ -5215,7 +5236,7 @@ var init_codex = __esm(async () => {
|
|
|
5215
5236
|
});
|
|
5216
5237
|
|
|
5217
5238
|
// ../../packages/core/src/version.ts
|
|
5218
|
-
var NEGOTIUM_VERSION = "0.22.
|
|
5239
|
+
var NEGOTIUM_VERSION = "0.22.5";
|
|
5219
5240
|
|
|
5220
5241
|
// ../../packages/core/src/agents/codex-native-multi-agent.ts
|
|
5221
5242
|
import { spawn as spawn2 } from "child_process";
|
|
@@ -18305,6 +18326,548 @@ var init_topic_config = __esm(async () => {
|
|
|
18305
18326
|
await init_api_topic_config();
|
|
18306
18327
|
});
|
|
18307
18328
|
|
|
18329
|
+
// ../../packages/core/src/storage/vault-request-gates.ts
|
|
18330
|
+
function initializeVaultRequestGateSchema() {
|
|
18331
|
+
db.exec(`
|
|
18332
|
+
CREATE TABLE IF NOT EXISTS vault_request_gates (
|
|
18333
|
+
gate_id TEXT PRIMARY KEY,
|
|
18334
|
+
topic_id TEXT NOT NULL REFERENCES api_topics(id) ON DELETE CASCADE,
|
|
18335
|
+
query_id TEXT,
|
|
18336
|
+
idempotency_key TEXT NOT NULL,
|
|
18337
|
+
body_hash TEXT NOT NULL,
|
|
18338
|
+
message_id TEXT NOT NULL UNIQUE,
|
|
18339
|
+
owner_id TEXT NOT NULL,
|
|
18340
|
+
state TEXT NOT NULL,
|
|
18341
|
+
outcome TEXT,
|
|
18342
|
+
answered_by TEXT,
|
|
18343
|
+
claim_source TEXT,
|
|
18344
|
+
created_at TEXT NOT NULL,
|
|
18345
|
+
updated_at TEXT NOT NULL
|
|
18346
|
+
);
|
|
18347
|
+
CREATE INDEX IF NOT EXISTS idx_vault_request_gates_idempotency
|
|
18348
|
+
ON vault_request_gates(topic_id, idempotency_key, created_at);
|
|
18349
|
+
CREATE UNIQUE INDEX IF NOT EXISTS idx_vault_request_gates_active_key
|
|
18350
|
+
ON vault_request_gates(topic_id, idempotency_key)
|
|
18351
|
+
WHERE state IN ('pending', 'claimed');
|
|
18352
|
+
`);
|
|
18353
|
+
}
|
|
18354
|
+
function fromRow2(row) {
|
|
18355
|
+
return {
|
|
18356
|
+
gateId: row.gate_id,
|
|
18357
|
+
topicId: row.topic_id,
|
|
18358
|
+
queryId: row.query_id ?? undefined,
|
|
18359
|
+
idempotencyKey: row.idempotency_key,
|
|
18360
|
+
bodyHash: row.body_hash,
|
|
18361
|
+
messageId: row.message_id,
|
|
18362
|
+
ownerId: row.owner_id,
|
|
18363
|
+
state: row.state,
|
|
18364
|
+
outcome: row.outcome ?? undefined,
|
|
18365
|
+
answeredBy: row.answered_by ?? undefined
|
|
18366
|
+
};
|
|
18367
|
+
}
|
|
18368
|
+
function latestGate2(topicId, idempotencyKey) {
|
|
18369
|
+
const row = db.query(`SELECT * FROM vault_request_gates
|
|
18370
|
+
WHERE topic_id = ? AND idempotency_key = ?
|
|
18371
|
+
ORDER BY rowid DESC LIMIT 1`).get(topicId, idempotencyKey);
|
|
18372
|
+
return row ? fromRow2(row) : null;
|
|
18373
|
+
}
|
|
18374
|
+
function expireVaultCard(topicId, messageId, editedAt) {
|
|
18375
|
+
const row = db.query(`SELECT vault_secret_request FROM api_messages
|
|
18376
|
+
WHERE topic_id = ? AND id = ? AND deleted = 0 AND kind = 'vault_secret_request'`).get(topicId, messageId);
|
|
18377
|
+
if (!row?.vault_secret_request)
|
|
18378
|
+
return null;
|
|
18379
|
+
const request = JSON.parse(row.vault_secret_request);
|
|
18380
|
+
if (request.status !== "pending")
|
|
18381
|
+
return null;
|
|
18382
|
+
const expired = { ...request, status: "expired" };
|
|
18383
|
+
db.query(`UPDATE api_messages SET vault_secret_request = ?, edited_at = ?
|
|
18384
|
+
WHERE topic_id = ? AND id = ? AND deleted = 0 AND kind = 'vault_secret_request'`).run(JSON.stringify(expired), editedAt, topicId, messageId);
|
|
18385
|
+
return { topicId, messageId, vaultSecretRequest: expired, editedAt };
|
|
18386
|
+
}
|
|
18387
|
+
function prepareVaultRequestGate(args) {
|
|
18388
|
+
return db.transaction(() => {
|
|
18389
|
+
const latest = latestGate2(args.topicId, args.idempotencyKey);
|
|
18390
|
+
if (latest?.bodyHash !== undefined && latest.bodyHash !== args.bodyHash) {
|
|
18391
|
+
return { outcome: "conflict", gate: latest };
|
|
18392
|
+
}
|
|
18393
|
+
if (latest?.state === "answered")
|
|
18394
|
+
return { outcome: "replay", gate: latest };
|
|
18395
|
+
if (latest && (latest.state === "pending" || latest.state === "claimed")) {
|
|
18396
|
+
return { outcome: "pending", gate: latest };
|
|
18397
|
+
}
|
|
18398
|
+
db.query(`INSERT INTO vault_request_gates
|
|
18399
|
+
(gate_id, topic_id, query_id, idempotency_key, body_hash, message_id, owner_id, state, created_at, updated_at)
|
|
18400
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, 'pending', ?, ?)`).run(args.gateId, args.topicId, args.queryId ?? null, args.idempotencyKey, args.bodyHash, args.messageId, args.ownerId, args.now, args.now);
|
|
18401
|
+
return {
|
|
18402
|
+
outcome: "created",
|
|
18403
|
+
gate: {
|
|
18404
|
+
gateId: args.gateId,
|
|
18405
|
+
topicId: args.topicId,
|
|
18406
|
+
queryId: args.queryId,
|
|
18407
|
+
idempotencyKey: args.idempotencyKey,
|
|
18408
|
+
bodyHash: args.bodyHash,
|
|
18409
|
+
messageId: args.messageId,
|
|
18410
|
+
ownerId: args.ownerId,
|
|
18411
|
+
state: "pending"
|
|
18412
|
+
}
|
|
18413
|
+
};
|
|
18414
|
+
}).immediate();
|
|
18415
|
+
}
|
|
18416
|
+
function quarantineVaultRequestGate(gateId, ownerId, now = new Date().toISOString()) {
|
|
18417
|
+
return db.transaction(() => {
|
|
18418
|
+
const row = db.query("SELECT * FROM vault_request_gates WHERE gate_id = ?").get(gateId);
|
|
18419
|
+
if (!row || row.owner_id !== ownerId || !["pending", "claimed"].includes(row.state)) {
|
|
18420
|
+
return null;
|
|
18421
|
+
}
|
|
18422
|
+
const result = db.query(`UPDATE vault_request_gates SET state = 'quarantined', updated_at = ?
|
|
18423
|
+
WHERE gate_id = ? AND owner_id = ? AND state IN ('pending', 'claimed')`).run(now, gateId, ownerId);
|
|
18424
|
+
if (Number(result.changes ?? 0) === 0)
|
|
18425
|
+
return null;
|
|
18426
|
+
return expireVaultCard(row.topic_id, row.message_id, now);
|
|
18427
|
+
}).immediate();
|
|
18428
|
+
}
|
|
18429
|
+
function quarantineForeignVaultRequestGates(liveOwnerIds, now = new Date().toISOString()) {
|
|
18430
|
+
return db.transaction(() => {
|
|
18431
|
+
const rows = db.query(`SELECT * FROM vault_request_gates
|
|
18432
|
+
WHERE state IN ('pending', 'claimed')`).all();
|
|
18433
|
+
const updates = [];
|
|
18434
|
+
for (const row of rows) {
|
|
18435
|
+
if (liveOwnerIds.has(row.owner_id))
|
|
18436
|
+
continue;
|
|
18437
|
+
if (row.state === "claimed" && row.outcome && row.answered_by) {
|
|
18438
|
+
db.query(`UPDATE vault_request_gates SET state = 'answered', updated_at = ?
|
|
18439
|
+
WHERE gate_id = ? AND state = 'claimed'`).run(now, row.gate_id);
|
|
18440
|
+
continue;
|
|
18441
|
+
}
|
|
18442
|
+
const result = db.query(`UPDATE vault_request_gates SET state = 'quarantined', updated_at = ?
|
|
18443
|
+
WHERE gate_id = ? AND state IN ('pending', 'claimed')`).run(now, row.gate_id);
|
|
18444
|
+
if (Number(result.changes ?? 0) === 0)
|
|
18445
|
+
continue;
|
|
18446
|
+
const update = expireVaultCard(row.topic_id, row.message_id, now);
|
|
18447
|
+
if (update)
|
|
18448
|
+
updates.push(update);
|
|
18449
|
+
}
|
|
18450
|
+
const legacyCards = db.query(`SELECT m.topic_id, m.id
|
|
18451
|
+
FROM api_messages m
|
|
18452
|
+
LEFT JOIN vault_request_gates g ON g.message_id = m.id
|
|
18453
|
+
WHERE m.kind = 'vault_secret_request'
|
|
18454
|
+
AND m.deleted = 0
|
|
18455
|
+
AND g.gate_id IS NULL`).all();
|
|
18456
|
+
for (const card of legacyCards) {
|
|
18457
|
+
const update = expireVaultCard(card.topic_id, card.id, now);
|
|
18458
|
+
if (update)
|
|
18459
|
+
updates.push(update);
|
|
18460
|
+
}
|
|
18461
|
+
return updates;
|
|
18462
|
+
}).immediate();
|
|
18463
|
+
}
|
|
18464
|
+
function claimVaultRequestGateAndAnswer(args) {
|
|
18465
|
+
return db.transaction(() => {
|
|
18466
|
+
const row = db.query(`SELECT * FROM vault_request_gates
|
|
18467
|
+
WHERE topic_id = ? AND message_id = ?
|
|
18468
|
+
ORDER BY rowid DESC LIMIT 1`).get(args.topicId, args.messageId);
|
|
18469
|
+
if (!row || row.owner_id !== args.ownerId || row.state !== "pending") {
|
|
18470
|
+
return { outcome: "unavailable" };
|
|
18471
|
+
}
|
|
18472
|
+
const message = db.query(`SELECT vault_secret_request FROM api_messages
|
|
18473
|
+
WHERE topic_id = ? AND id = ? AND deleted = 0 AND kind = 'vault_secret_request'`).get(args.topicId, args.messageId);
|
|
18474
|
+
if (!message?.vault_secret_request)
|
|
18475
|
+
return { outcome: "unavailable" };
|
|
18476
|
+
const request = JSON.parse(message.vault_secret_request);
|
|
18477
|
+
if (request.status !== "pending")
|
|
18478
|
+
return { outcome: "unavailable" };
|
|
18479
|
+
const claimed = db.query(`UPDATE vault_request_gates
|
|
18480
|
+
SET state = 'answered', outcome = ?, answered_by = ?, claim_source = ?, updated_at = ?
|
|
18481
|
+
WHERE gate_id = ? AND owner_id = ? AND state = 'pending'`).run(args.outcome, args.userId, args.source, args.now, row.gate_id, args.ownerId);
|
|
18482
|
+
if (Number(claimed.changes ?? 0) === 0)
|
|
18483
|
+
return { outcome: "unavailable" };
|
|
18484
|
+
const answered = {
|
|
18485
|
+
...request,
|
|
18486
|
+
status: args.outcome,
|
|
18487
|
+
answeredBy: args.userId
|
|
18488
|
+
};
|
|
18489
|
+
db.query(`UPDATE api_messages SET vault_secret_request = ?, edited_at = ?
|
|
18490
|
+
WHERE topic_id = ? AND id = ? AND deleted = 0 AND kind = 'vault_secret_request'`).run(JSON.stringify(answered), args.now, args.topicId, args.messageId);
|
|
18491
|
+
return {
|
|
18492
|
+
outcome: "claimed",
|
|
18493
|
+
gate: fromRow2({
|
|
18494
|
+
...row,
|
|
18495
|
+
state: "answered",
|
|
18496
|
+
outcome: args.outcome,
|
|
18497
|
+
answered_by: args.userId
|
|
18498
|
+
}),
|
|
18499
|
+
vaultSecretRequest: answered,
|
|
18500
|
+
editedAt: args.now
|
|
18501
|
+
};
|
|
18502
|
+
}).immediate();
|
|
18503
|
+
}
|
|
18504
|
+
function cancelVaultRequestGate(topicId, messageId, ownerId, now = new Date().toISOString()) {
|
|
18505
|
+
return db.transaction(() => {
|
|
18506
|
+
const row = db.query(`SELECT * FROM vault_request_gates
|
|
18507
|
+
WHERE topic_id = ? AND message_id = ? ORDER BY rowid DESC LIMIT 1`).get(topicId, messageId);
|
|
18508
|
+
if (!row || row.owner_id !== ownerId || row.state !== "pending")
|
|
18509
|
+
return null;
|
|
18510
|
+
const result = db.query(`UPDATE vault_request_gates SET state = 'cancelled', updated_at = ?
|
|
18511
|
+
WHERE gate_id = ? AND owner_id = ? AND state = 'pending'`).run(now, row.gate_id, ownerId);
|
|
18512
|
+
if (Number(result.changes ?? 0) === 0)
|
|
18513
|
+
return null;
|
|
18514
|
+
return expireVaultCard(topicId, messageId, now);
|
|
18515
|
+
}).immediate();
|
|
18516
|
+
}
|
|
18517
|
+
var init_vault_request_gates = __esm(async () => {
|
|
18518
|
+
await __promiseAll([
|
|
18519
|
+
init_forum_db(),
|
|
18520
|
+
init_storage_host()
|
|
18521
|
+
]);
|
|
18522
|
+
registerStorageSchemaInitializer(initializeVaultRequestGateSchema, 32);
|
|
18523
|
+
});
|
|
18524
|
+
|
|
18525
|
+
// ../../packages/core/src/agents/mcp-tools/request-vault.ts
|
|
18526
|
+
import { createHash as createHash12, randomUUID as randomUUID17 } from "crypto";
|
|
18527
|
+
import { z as z4 } from "zod";
|
|
18528
|
+
function requestBodyHash(key, description) {
|
|
18529
|
+
return createHash12("sha256").update(JSON.stringify({ key, description })).digest("hex");
|
|
18530
|
+
}
|
|
18531
|
+
function answerToolResult2(key, outcome) {
|
|
18532
|
+
if (outcome === "no_answer") {
|
|
18533
|
+
return errorResult("The AI turn ended before the user answered.");
|
|
18534
|
+
}
|
|
18535
|
+
if (outcome === "declined") {
|
|
18536
|
+
return errorResult(`User declined to provide a value for ${key}.`);
|
|
18537
|
+
}
|
|
18538
|
+
return textResult(`User stored a value for ${key} in the vault. Reference it as {{${key}}} directly in supported transient tool inputs. Never ask the user to repeat the raw value in chat.`);
|
|
18539
|
+
}
|
|
18540
|
+
function createRequestVaultRuntime(host) {
|
|
18541
|
+
const pendingRequests = new Map;
|
|
18542
|
+
const { ownerId } = host.runtime;
|
|
18543
|
+
const createId = () => host.runtime.createId();
|
|
18544
|
+
const now = () => host.runtime.now();
|
|
18545
|
+
const isProcessLeaseAlive = (lease) => host.durability.processLeases.isAlive(lease);
|
|
18546
|
+
let gateOwnerLease = null;
|
|
18547
|
+
function broadcastGateUpdates(updates) {
|
|
18548
|
+
for (const update of updates)
|
|
18549
|
+
host.messaging.publication.publishCardUpdate(update);
|
|
18550
|
+
}
|
|
18551
|
+
function startGateOwner() {
|
|
18552
|
+
if (gateOwnerLease)
|
|
18553
|
+
return;
|
|
18554
|
+
gateOwnerLease = host.durability.processLeases.acquire(`${VAULT_GATE_LEASE_ROLE_PREFIX}${ownerId}`, ownerId);
|
|
18555
|
+
if (!gateOwnerLease)
|
|
18556
|
+
throw new Error("failed to acquire the vault-request gate owner lease");
|
|
18557
|
+
}
|
|
18558
|
+
function stopGateOwner() {
|
|
18559
|
+
gateOwnerLease?.stop();
|
|
18560
|
+
gateOwnerLease = null;
|
|
18561
|
+
}
|
|
18562
|
+
function reconcilePendingGates() {
|
|
18563
|
+
startGateOwner();
|
|
18564
|
+
host.durability.processLeases.removeDead(VAULT_GATE_LEASE_ROLE_PREFIX);
|
|
18565
|
+
const liveOwners = new Set(host.durability.processLeases.list(VAULT_GATE_LEASE_ROLE_PREFIX).filter(isProcessLeaseAlive).map((lease) => lease.ownerId));
|
|
18566
|
+
const updates = host.durability.gates.quarantineForeign(liveOwners, now());
|
|
18567
|
+
broadcastGateUpdates(updates);
|
|
18568
|
+
return updates.length;
|
|
18569
|
+
}
|
|
18570
|
+
function answerPendingRequest(topicId, messageId, userId, value) {
|
|
18571
|
+
reconcilePendingGates();
|
|
18572
|
+
const topic = host.topics.getTopic(topicId);
|
|
18573
|
+
if (!topic?.participants.some((participant) => participant.userId === userId)) {
|
|
18574
|
+
return { ok: false, error: "User is not a member of this topic." };
|
|
18575
|
+
}
|
|
18576
|
+
const existing = host.messaging.persistence.getMessage(topicId, messageId);
|
|
18577
|
+
const request = existing?.kind === "vault_secret_request" ? existing.vaultSecretRequest : undefined;
|
|
18578
|
+
if (!existing || existing.deleted || !request) {
|
|
18579
|
+
return { ok: false, error: "Vault request card not found." };
|
|
18580
|
+
}
|
|
18581
|
+
if (request.status !== "pending") {
|
|
18582
|
+
return { ok: false, error: "This request is no longer awaiting an answer." };
|
|
18583
|
+
}
|
|
18584
|
+
const pending = pendingRequests.get(messageId);
|
|
18585
|
+
if (!pending || pending.topicId !== topicId) {
|
|
18586
|
+
return { ok: false, error: "This request is no longer awaiting an answer." };
|
|
18587
|
+
}
|
|
18588
|
+
const declining = value === null;
|
|
18589
|
+
if (!declining) {
|
|
18590
|
+
const bytes = Buffer.byteLength(value, "utf8");
|
|
18591
|
+
if (bytes < VAULT_VALUE_MIN_BYTES || bytes > VAULT_VALUE_MAX_BYTES) {
|
|
18592
|
+
return {
|
|
18593
|
+
ok: false,
|
|
18594
|
+
error: `Value must be between ${VAULT_VALUE_MIN_BYTES} and ${VAULT_VALUE_MAX_BYTES} bytes.`
|
|
18595
|
+
};
|
|
18596
|
+
}
|
|
18597
|
+
}
|
|
18598
|
+
if (!declining) {
|
|
18599
|
+
try {
|
|
18600
|
+
host.vault.set(userId, pending.key, value, pending.description);
|
|
18601
|
+
} catch (error) {
|
|
18602
|
+
return {
|
|
18603
|
+
ok: false,
|
|
18604
|
+
error: `Failed to store the value: ${error instanceof Error ? error.message : String(error)}`
|
|
18605
|
+
};
|
|
18606
|
+
}
|
|
18607
|
+
}
|
|
18608
|
+
const outcome = declining ? "declined" : "fulfilled";
|
|
18609
|
+
let claim;
|
|
18610
|
+
try {
|
|
18611
|
+
claim = host.durability.gates.claimAndAnswer({
|
|
18612
|
+
topicId,
|
|
18613
|
+
messageId,
|
|
18614
|
+
outcome,
|
|
18615
|
+
userId,
|
|
18616
|
+
ownerId,
|
|
18617
|
+
source: "host-control",
|
|
18618
|
+
now: now()
|
|
18619
|
+
});
|
|
18620
|
+
} catch {
|
|
18621
|
+
return { ok: false, error: "This request could not be claimed safely. Retry once." };
|
|
18622
|
+
}
|
|
18623
|
+
if (claim.outcome !== "claimed") {
|
|
18624
|
+
return { ok: false, error: "This request is no longer awaiting an answer." };
|
|
18625
|
+
}
|
|
18626
|
+
pendingRequests.delete(messageId);
|
|
18627
|
+
const answerMessage = {
|
|
18628
|
+
id: createId(),
|
|
18629
|
+
topicId,
|
|
18630
|
+
authorId: userId,
|
|
18631
|
+
text: outcome === "fulfilled" ? `Provided a value for ${pending.key}.` : `Declined to provide a value for ${pending.key}.`,
|
|
18632
|
+
parentId: messageId,
|
|
18633
|
+
...existing.threadRootId ? { threadRootId: existing.threadRootId } : {},
|
|
18634
|
+
createdAt: now()
|
|
18635
|
+
};
|
|
18636
|
+
try {
|
|
18637
|
+
host.messaging.publication.publishCardUpdate({
|
|
18638
|
+
topicId,
|
|
18639
|
+
messageId,
|
|
18640
|
+
vaultSecretRequest: claim.vaultSecretRequest,
|
|
18641
|
+
editedAt: claim.editedAt
|
|
18642
|
+
});
|
|
18643
|
+
host.messaging.persistence.appendMessage(answerMessage);
|
|
18644
|
+
host.messaging.publication.publishMessage(topicId, answerMessage);
|
|
18645
|
+
} catch {}
|
|
18646
|
+
pending.resolve({ outcome, userId });
|
|
18647
|
+
return { ok: true, queryId: pending.queryId, outcome, answerMessage };
|
|
18648
|
+
}
|
|
18649
|
+
function cancelPendingRequests(topicId, queryId) {
|
|
18650
|
+
for (const [messageId, pending] of pendingRequests) {
|
|
18651
|
+
if (pending.topicId === topicId && pending.queryId === queryId) {
|
|
18652
|
+
pendingRequests.delete(messageId);
|
|
18653
|
+
const update = host.durability.gates.cancel(topicId, messageId, ownerId, now());
|
|
18654
|
+
if (update)
|
|
18655
|
+
broadcastGateUpdates([update]);
|
|
18656
|
+
pending.resolve({ outcome: "no_answer", userId: "" });
|
|
18657
|
+
}
|
|
18658
|
+
}
|
|
18659
|
+
}
|
|
18660
|
+
function appendRequestMessage(ctx, key, description, messageId) {
|
|
18661
|
+
const cfg = host.topics.getConfig(ctx.topicId);
|
|
18662
|
+
const vaultSecretRequest = { key, description, status: "pending" };
|
|
18663
|
+
const message = {
|
|
18664
|
+
id: messageId,
|
|
18665
|
+
topicId: ctx.topicId,
|
|
18666
|
+
authorId: "ai",
|
|
18667
|
+
text: `Vault secret requested: ${key}`,
|
|
18668
|
+
queryId: ctx.queryId,
|
|
18669
|
+
agentType: ctx.agent,
|
|
18670
|
+
model: ctx.model ?? cfg?.model ?? "unknown",
|
|
18671
|
+
kind: "vault_secret_request",
|
|
18672
|
+
vaultSecretRequest,
|
|
18673
|
+
...ctx.threadRootId ? { threadRootId: ctx.threadRootId } : {},
|
|
18674
|
+
createdAt: now()
|
|
18675
|
+
};
|
|
18676
|
+
host.messaging.persistence.appendMessage(message);
|
|
18677
|
+
return message;
|
|
18678
|
+
}
|
|
18679
|
+
async function requestVaultSecret(ctx, input) {
|
|
18680
|
+
if (ctx.unsupportedSurface) {
|
|
18681
|
+
return errorResult("Error: this surface cannot mask secret input safely (there is no password field here, so a typed value would land in plaintext chat history). Ask the user to provide this value from the Terminal app or Otium instead.");
|
|
18682
|
+
}
|
|
18683
|
+
reconcilePendingGates();
|
|
18684
|
+
const topic = host.topics.getTopic(ctx.topicId);
|
|
18685
|
+
if (!topic)
|
|
18686
|
+
return errorResult(`Error: topic '${ctx.topicId}' not found.`);
|
|
18687
|
+
if (!topic.participants.some((participant) => participant.userId === ctx.userId)) {
|
|
18688
|
+
return errorResult("Error: user is not a member of this topic.");
|
|
18689
|
+
}
|
|
18690
|
+
if (typeof input.key !== "string" || !input.key.trim()) {
|
|
18691
|
+
return errorResult("Error: key is required.");
|
|
18692
|
+
}
|
|
18693
|
+
const key = normalizeVaultKey(input.key);
|
|
18694
|
+
if (!VAULT_KEY_PATTERN.test(key)) {
|
|
18695
|
+
return errorResult("Error: key must start with a letter and contain only uppercase letters, digits, and underscores (max 128 chars).");
|
|
18696
|
+
}
|
|
18697
|
+
if (typeof input.description !== "string" || !input.description.trim()) {
|
|
18698
|
+
return errorResult("Error: description is required so the user knows what to type.");
|
|
18699
|
+
}
|
|
18700
|
+
const description = input.description.trim().slice(0, MAX_DESCRIPTION_CHARS);
|
|
18701
|
+
if (input.idempotency_key !== undefined && (typeof input.idempotency_key !== "string" || !input.idempotency_key.trim())) {
|
|
18702
|
+
return errorResult("Error: idempotency_key must be a non-empty string.");
|
|
18703
|
+
}
|
|
18704
|
+
const rawIdempotencyKey = typeof input.idempotency_key === "string" ? input.idempotency_key.trim() : createId();
|
|
18705
|
+
if (rawIdempotencyKey.length > MAX_IDEMPOTENCY_KEY_CHARS2) {
|
|
18706
|
+
return errorResult(`Error: idempotency_key must be at most ${MAX_IDEMPOTENCY_KEY_CHARS2} characters.`);
|
|
18707
|
+
}
|
|
18708
|
+
const bodyHash = requestBodyHash(key, description);
|
|
18709
|
+
const createGate = () => {
|
|
18710
|
+
const suffix = createId();
|
|
18711
|
+
return host.durability.gates.prepare({
|
|
18712
|
+
gateId: `vault-request-gate-${suffix}`,
|
|
18713
|
+
topicId: ctx.topicId,
|
|
18714
|
+
queryId: ctx.queryId,
|
|
18715
|
+
idempotencyKey: rawIdempotencyKey,
|
|
18716
|
+
bodyHash,
|
|
18717
|
+
messageId: `vault-request-${ctx.queryId ?? "runtime"}-${suffix}`,
|
|
18718
|
+
ownerId,
|
|
18719
|
+
now: now()
|
|
18720
|
+
});
|
|
18721
|
+
};
|
|
18722
|
+
let prepared = createGate();
|
|
18723
|
+
if (prepared.outcome === "conflict") {
|
|
18724
|
+
return errorResult("Error: idempotency_conflict. The same idempotency_key was already used with a different key or description.");
|
|
18725
|
+
}
|
|
18726
|
+
if (prepared.outcome === "replay") {
|
|
18727
|
+
if (!prepared.gate.outcome || !prepared.gate.answeredBy) {
|
|
18728
|
+
return errorResult("Error: the stored request_vault_secret replay is incomplete.");
|
|
18729
|
+
}
|
|
18730
|
+
return answerToolResult2(key, prepared.gate.outcome);
|
|
18731
|
+
}
|
|
18732
|
+
if (prepared.outcome === "pending") {
|
|
18733
|
+
if (prepared.gate.ownerId !== ownerId) {
|
|
18734
|
+
return errorResult("Error: this request_vault_secret is pending in another healthy runtime process.");
|
|
18735
|
+
}
|
|
18736
|
+
const existingPending = pendingRequests.get(prepared.gate.messageId);
|
|
18737
|
+
if (existingPending)
|
|
18738
|
+
return answerToolResult2(key, (await existingPending.promise).outcome);
|
|
18739
|
+
if (prepared.gate.state === "claimed") {
|
|
18740
|
+
return errorResult("Error: this request_vault_secret is already being finalized.");
|
|
18741
|
+
}
|
|
18742
|
+
const update = host.durability.gates.quarantine(prepared.gate.gateId, ownerId, now());
|
|
18743
|
+
if (update)
|
|
18744
|
+
broadcastGateUpdates([update]);
|
|
18745
|
+
prepared = createGate();
|
|
18746
|
+
}
|
|
18747
|
+
if (prepared.outcome !== "created") {
|
|
18748
|
+
return errorResult("Error: failed to create a durable request_vault_secret gate.");
|
|
18749
|
+
}
|
|
18750
|
+
if (host.vault.hasKey(ctx.userId, key)) {
|
|
18751
|
+
host.durability.gates.quarantine(prepared.gate.gateId, ownerId, now());
|
|
18752
|
+
return errorResult(`Error: vault key '${key}' already exists. Pick a different key, or ask the user to update it from their Vault manager.`);
|
|
18753
|
+
}
|
|
18754
|
+
let message;
|
|
18755
|
+
try {
|
|
18756
|
+
message = appendRequestMessage(ctx, key, description, prepared.gate.messageId);
|
|
18757
|
+
} catch (error) {
|
|
18758
|
+
host.durability.gates.quarantine(prepared.gate.gateId, ownerId, now());
|
|
18759
|
+
return errorResult(`Error: failed to persist request_vault_secret: ${error instanceof Error ? error.message : String(error)}`);
|
|
18760
|
+
}
|
|
18761
|
+
let resolveAnswer;
|
|
18762
|
+
const promise = new Promise((resolve17) => {
|
|
18763
|
+
resolveAnswer = resolve17;
|
|
18764
|
+
});
|
|
18765
|
+
pendingRequests.set(message.id, {
|
|
18766
|
+
topicId: ctx.topicId,
|
|
18767
|
+
queryId: ctx.queryId,
|
|
18768
|
+
messageId: message.id,
|
|
18769
|
+
key,
|
|
18770
|
+
description,
|
|
18771
|
+
promise,
|
|
18772
|
+
resolve: resolveAnswer
|
|
18773
|
+
});
|
|
18774
|
+
try {
|
|
18775
|
+
host.messaging.publication.publishMessage(ctx.topicId, message);
|
|
18776
|
+
} catch (error) {
|
|
18777
|
+
pendingRequests.delete(message.id);
|
|
18778
|
+
try {
|
|
18779
|
+
host.durability.gates.quarantine(prepared.gate.gateId, ownerId, now());
|
|
18780
|
+
} catch {}
|
|
18781
|
+
return errorResult(`Error: failed to publish request_vault_secret: ${error instanceof Error ? error.message : String(error)}`);
|
|
18782
|
+
}
|
|
18783
|
+
return answerToolResult2(key, (await promise).outcome);
|
|
18784
|
+
}
|
|
18785
|
+
function createToolDefinition(ctx) {
|
|
18786
|
+
return {
|
|
18787
|
+
name: "request_vault_secret",
|
|
18788
|
+
description: "Ask the user to store a brand-new secret value in their Vault. You choose the key name and a short description of what to paste; the user types only the value on a surface that can mask it, and you never see the raw value. Use this instead of asking the user to paste a secret directly into chat. After it succeeds, reference the value as {{KEY}} in supported transient tool inputs.",
|
|
18789
|
+
schema: {
|
|
18790
|
+
key: z4.string().describe("New Vault key name, e.g. STRIPE_API_KEY. Letters/digits/underscores, must start with a letter."),
|
|
18791
|
+
description: z4.string().max(MAX_DESCRIPTION_CHARS).describe("One sentence telling the user what value to paste and where it's from."),
|
|
18792
|
+
idempotency_key: z4.string().max(MAX_IDEMPOTENCY_KEY_CHARS2).optional().describe("Optional retry key. Reusing it with the same key/description replays the existing result; reusing it with different content fails.")
|
|
18793
|
+
},
|
|
18794
|
+
async handler(input) {
|
|
18795
|
+
return requestVaultSecret(ctx, input);
|
|
18796
|
+
}
|
|
18797
|
+
};
|
|
18798
|
+
}
|
|
18799
|
+
return {
|
|
18800
|
+
startGateOwner,
|
|
18801
|
+
stopGateOwner,
|
|
18802
|
+
reconcilePendingGates,
|
|
18803
|
+
answerPendingRequest,
|
|
18804
|
+
cancelPendingRequests,
|
|
18805
|
+
createToolDefinition
|
|
18806
|
+
};
|
|
18807
|
+
}
|
|
18808
|
+
function cancelPendingVaultSecretRequests(topicId, queryId) {
|
|
18809
|
+
defaultRequestVaultRuntime.cancelPendingRequests(topicId, queryId);
|
|
18810
|
+
}
|
|
18811
|
+
var MAX_DESCRIPTION_CHARS, MAX_IDEMPOTENCY_KEY_CHARS2 = 200, VAULT_GATE_OWNER_ID, VAULT_GATE_LEASE_ROLE_PREFIX = "vault-request-gate:", defaultRequestVaultDurabilityHost, defaultRequestVaultRuntime;
|
|
18812
|
+
var init_request_vault = __esm(async () => {
|
|
18813
|
+
init_common();
|
|
18814
|
+
await __promiseAll([
|
|
18815
|
+
init_bus(),
|
|
18816
|
+
init_api_messages(),
|
|
18817
|
+
init_api_topic_config(),
|
|
18818
|
+
init_api_topics(),
|
|
18819
|
+
init_runtime_process_leases(),
|
|
18820
|
+
init_vault(),
|
|
18821
|
+
init_vault_request_gates()
|
|
18822
|
+
]);
|
|
18823
|
+
MAX_DESCRIPTION_CHARS = VAULT_DESCRIPTION_MAX_LENGTH;
|
|
18824
|
+
VAULT_GATE_OWNER_ID = `request-vault-${process.pid}-${randomUUID17()}`;
|
|
18825
|
+
defaultRequestVaultDurabilityHost = {
|
|
18826
|
+
gates: {
|
|
18827
|
+
prepare: prepareVaultRequestGate,
|
|
18828
|
+
claimAndAnswer: claimVaultRequestGateAndAnswer,
|
|
18829
|
+
cancel: cancelVaultRequestGate,
|
|
18830
|
+
quarantine: quarantineVaultRequestGate,
|
|
18831
|
+
quarantineForeign: quarantineForeignVaultRequestGates
|
|
18832
|
+
},
|
|
18833
|
+
processLeases: {
|
|
18834
|
+
acquire: (role, ownerId) => acquireRuntimeProcessLease(role, { ownerId }),
|
|
18835
|
+
list: listRuntimeProcessLeases,
|
|
18836
|
+
isAlive: isRuntimeProcessLeaseAlive,
|
|
18837
|
+
removeDead: removeDeadRuntimeProcessLeases
|
|
18838
|
+
}
|
|
18839
|
+
};
|
|
18840
|
+
defaultRequestVaultRuntime = createRequestVaultRuntime({
|
|
18841
|
+
messaging: {
|
|
18842
|
+
persistence: {
|
|
18843
|
+
getMessage: getApiMessage,
|
|
18844
|
+
appendMessage: appendApiMessage
|
|
18845
|
+
},
|
|
18846
|
+
publication: {
|
|
18847
|
+
publishMessage: (topicId, message) => WsHub.get().broadcastMessage(topicId, message),
|
|
18848
|
+
publishCardUpdate: (update) => WsHub.get().broadcastMessageUpdated(update.topicId, update.messageId, {
|
|
18849
|
+
vaultSecretRequest: update.vaultSecretRequest,
|
|
18850
|
+
editedAt: update.editedAt
|
|
18851
|
+
})
|
|
18852
|
+
}
|
|
18853
|
+
},
|
|
18854
|
+
topics: {
|
|
18855
|
+
getTopic,
|
|
18856
|
+
getConfig: getApiTopicConfig
|
|
18857
|
+
},
|
|
18858
|
+
vault: {
|
|
18859
|
+
hasKey: vaultHasKey,
|
|
18860
|
+
set: vaultSet
|
|
18861
|
+
},
|
|
18862
|
+
durability: defaultRequestVaultDurabilityHost,
|
|
18863
|
+
runtime: {
|
|
18864
|
+
ownerId: VAULT_GATE_OWNER_ID,
|
|
18865
|
+
createId: randomUUID17,
|
|
18866
|
+
now: () => new Date().toISOString()
|
|
18867
|
+
}
|
|
18868
|
+
});
|
|
18869
|
+
});
|
|
18870
|
+
|
|
18308
18871
|
// ../../packages/core/src/mcp/peer-bridge.ts
|
|
18309
18872
|
function flushPeerRuntimeEvents(localTopicId) {
|
|
18310
18873
|
return activeBridge2?.flushEvents?.(localTopicId) ?? Promise.resolve(true);
|
|
@@ -18679,7 +19242,7 @@ var init_visuals = __esm(async () => {
|
|
|
18679
19242
|
});
|
|
18680
19243
|
|
|
18681
19244
|
// ../../packages/core/src/runtime/turn-event-stream.ts
|
|
18682
|
-
import { randomUUID as
|
|
19245
|
+
import { randomUUID as randomUUID18 } from "crypto";
|
|
18683
19246
|
import { realpathSync as realpathSync6, statSync as statSync9 } from "fs";
|
|
18684
19247
|
import { isAbsolute as isAbsolute7, resolve as resolve18 } from "path";
|
|
18685
19248
|
function sessionEventMatchesCurrentExecution(topicId, queryId, agent, model) {
|
|
@@ -18786,7 +19349,7 @@ async function runTurnEventStream(topicId, topicTitle, queryId, events, control,
|
|
|
18786
19349
|
if (silent || !text2.trim())
|
|
18787
19350
|
return null;
|
|
18788
19351
|
const message = {
|
|
18789
|
-
id:
|
|
19352
|
+
id: randomUUID18(),
|
|
18790
19353
|
topicId,
|
|
18791
19354
|
authorId: "ai",
|
|
18792
19355
|
text: text2,
|
|
@@ -19266,6 +19829,7 @@ ${JSON.stringify(event.input ?? {})}`);
|
|
|
19266
19829
|
const stillCurrent = getRoomQuery(roomId)?.queryId === queryId;
|
|
19267
19830
|
clearRoomQuery(roomId, queryId);
|
|
19268
19831
|
cancelPendingAskUserQuestions(topicId, queryId);
|
|
19832
|
+
cancelPendingVaultSecretRequests(topicId, queryId);
|
|
19269
19833
|
if (stillCurrent && roomId === topicId)
|
|
19270
19834
|
clearQueryState(userId, topicId, topicTitle);
|
|
19271
19835
|
if (!silent && stillCurrent)
|
|
@@ -19309,6 +19873,7 @@ var init_turn_event_stream = __esm(async () => {
|
|
|
19309
19873
|
init_idle_archiver(),
|
|
19310
19874
|
init_idle_compact(),
|
|
19311
19875
|
init_ask_user(),
|
|
19876
|
+
init_request_vault(),
|
|
19312
19877
|
init_spawn_subagent(),
|
|
19313
19878
|
init_registry(),
|
|
19314
19879
|
init_bus(),
|
|
@@ -19463,7 +20028,7 @@ __export(exports_turn_runner, {
|
|
|
19463
20028
|
FALLBACK_ORDER: () => FALLBACK_ORDER,
|
|
19464
20029
|
AGENT_DISPLAY_NAME: () => AGENT_DISPLAY_NAME
|
|
19465
20030
|
});
|
|
19466
|
-
import { randomUUID as
|
|
20031
|
+
import { randomUUID as randomUUID19 } from "crypto";
|
|
19467
20032
|
import { existsSync as existsSync22, mkdirSync as mkdirSync19, readdirSync as readdirSync5, statSync as statSync10 } from "fs";
|
|
19468
20033
|
import { join as join31 } from "path";
|
|
19469
20034
|
function withDefaultPlaywright(configuredMcp, isManager) {
|
|
@@ -19488,7 +20053,7 @@ ${reminderPrompt}` };
|
|
|
19488
20053
|
}
|
|
19489
20054
|
function appendSystemMessage(topicId, text2, threadRootId) {
|
|
19490
20055
|
const message = {
|
|
19491
|
-
id:
|
|
20056
|
+
id: randomUUID19(),
|
|
19492
20057
|
topicId,
|
|
19493
20058
|
authorId: "system",
|
|
19494
20059
|
text: text2,
|
|
@@ -19510,7 +20075,7 @@ function notifyPlaywrightUnavailable(topicId) {
|
|
|
19510
20075
|
}
|
|
19511
20076
|
function appendAskReplyMessage(topicId, text2, sourceLabel, body, kind, agentType, threadRootId) {
|
|
19512
20077
|
const message = {
|
|
19513
|
-
id:
|
|
20078
|
+
id: randomUUID19(),
|
|
19514
20079
|
topicId,
|
|
19515
20080
|
authorId: "ai",
|
|
19516
20081
|
text: text2,
|
|
@@ -19785,8 +20350,10 @@ function resolveTurnToolCapabilities(topicId, params) {
|
|
|
19785
20350
|
};
|
|
19786
20351
|
}
|
|
19787
20352
|
function resolveTurnHostMcpServers(topic, sessionType) {
|
|
19788
|
-
|
|
19789
|
-
|
|
20353
|
+
if (topic.isSubagent)
|
|
20354
|
+
return;
|
|
20355
|
+
const ownScope = topic.kind === "manager" ? "manager" : "forum";
|
|
20356
|
+
if ((sessionType ?? ownScope) !== ownScope)
|
|
19790
20357
|
return;
|
|
19791
20358
|
return getTopicHostMcpGrant(topic.id) ?? undefined;
|
|
19792
20359
|
}
|
|
@@ -20031,7 +20598,7 @@ function startAiTurn(params) {
|
|
|
20031
20598
|
const askReplySources = params.askReplySources;
|
|
20032
20599
|
const sessionRetried = params._sessionRetried === true;
|
|
20033
20600
|
const emptyResponseRetried = params._emptyResponseRetried === true;
|
|
20034
|
-
const queryId = asTurnId(params._queryId ??
|
|
20601
|
+
const queryId = asTurnId(params._queryId ?? randomUUID19());
|
|
20035
20602
|
const roomId = turnConcurrency === "isolated" ? isolatedTurnRoomId(topicId, queryId) : asTurnSlotKey(topicId);
|
|
20036
20603
|
const currentRuntimeEpoch = getRuntimeTopicEpoch(topic.id);
|
|
20037
20604
|
const runtimeEpoch = params._runtimeEpoch ?? currentRuntimeEpoch;
|
|
@@ -20802,7 +21369,7 @@ function triggerTopicAiTurn(topicId, userId, prompt, agentType, opts) {
|
|
|
20802
21369
|
if (!opts?.silent && !opts?.hideInjectMessage) {
|
|
20803
21370
|
const now = new Date().toISOString();
|
|
20804
21371
|
const injectMsg = {
|
|
20805
|
-
id: opts?.requestId ? `tell-${opts.requestId}` : `tell-${
|
|
21372
|
+
id: opts?.requestId ? `tell-${opts.requestId}` : `tell-${randomUUID19()}`,
|
|
20806
21373
|
topicId,
|
|
20807
21374
|
authorId: opts?.injectAuthorId ?? userId,
|
|
20808
21375
|
sourceAdapter: opts?.injectSourceAdapter,
|
|
@@ -20924,7 +21491,7 @@ var init_turn_runner = __esm(async () => {
|
|
|
20924
21491
|
});
|
|
20925
21492
|
|
|
20926
21493
|
// ../../packages/core/src/agents/mcp-tools/spawn-subagent.ts
|
|
20927
|
-
import { z as
|
|
21494
|
+
import { z as z5 } from "zod";
|
|
20928
21495
|
function activeQueryUserId(topic) {
|
|
20929
21496
|
return topic?.participants.find((participant) => hasActiveQuery(participant.userId, topic.id))?.userId;
|
|
20930
21497
|
}
|
|
@@ -21491,7 +22058,7 @@ ${card.task}${reportInstruction}`;
|
|
|
21491
22058
|
name: "start_subagent",
|
|
21492
22059
|
description: "Start a prepared descendant subagent task.",
|
|
21493
22060
|
schema: {
|
|
21494
|
-
topic_id:
|
|
22061
|
+
topic_id: z5.string().describe("Prepared subagent room id.")
|
|
21495
22062
|
},
|
|
21496
22063
|
async handler(input) {
|
|
21497
22064
|
const result = ownedSubagentTree(ctx);
|
|
@@ -21511,7 +22078,7 @@ ${card.task}${reportInstruction}`;
|
|
|
21511
22078
|
name: "delete_subagent",
|
|
21512
22079
|
description: "Permanently delete one descendant subagent room managed by the current room. " + "This removes its conversation, workspace, runtime state, and topic. Keep it when follow-up work is likely.",
|
|
21513
22080
|
schema: {
|
|
21514
|
-
topic_id:
|
|
22081
|
+
topic_id: z5.string().describe("Exact subagent room id returned by list_subagents; names are not accepted.")
|
|
21515
22082
|
},
|
|
21516
22083
|
async handler(input) {
|
|
21517
22084
|
const result = ownedSubagentTree(ctx);
|
|
@@ -21535,8 +22102,8 @@ ${card.task}${reportInstruction}`;
|
|
|
21535
22102
|
name: "grant_subagent_tell",
|
|
21536
22103
|
description: "Allow one descendant subagent to tell_session another topic in this managed subagent tree. " + "Direct-parent reporting is always allowed and does not need a grant.",
|
|
21537
22104
|
schema: {
|
|
21538
|
-
subagent_topic_id:
|
|
21539
|
-
target_topic_id:
|
|
22105
|
+
subagent_topic_id: z5.string().describe("Source descendant subagent topic id."),
|
|
22106
|
+
target_topic_id: z5.string().describe("Target topic id: this manager room or another descendant in its tree.")
|
|
21540
22107
|
},
|
|
21541
22108
|
async handler(input) {
|
|
21542
22109
|
const result = ownedSubagentTree(ctx);
|
|
@@ -21561,8 +22128,8 @@ ${card.task}${reportInstruction}`;
|
|
|
21561
22128
|
name: "revoke_subagent_tell",
|
|
21562
22129
|
description: "Remove an extra tell_session connection previously granted to a descendant subagent.",
|
|
21563
22130
|
schema: {
|
|
21564
|
-
subagent_topic_id:
|
|
21565
|
-
target_topic_id:
|
|
22131
|
+
subagent_topic_id: z5.string().describe("Source descendant subagent topic id."),
|
|
22132
|
+
target_topic_id: z5.string().describe("Granted target topic id.")
|
|
21566
22133
|
},
|
|
21567
22134
|
async handler(input) {
|
|
21568
22135
|
const result = ownedSubagentTree(ctx);
|
|
@@ -21584,18 +22151,18 @@ ${card.task}${reportInstruction}`;
|
|
|
21584
22151
|
}
|
|
21585
22152
|
function createSpawnSubagentToolDefinition(ctx, options = {}) {
|
|
21586
22153
|
const executionOverrideSchema = options.executionOverrides === false ? {} : {
|
|
21587
|
-
agent:
|
|
21588
|
-
model:
|
|
22154
|
+
agent: z5.enum(["claude", "codex", "maestro"]).optional().describe("Agent backend override. Defaults to this room's agent."),
|
|
22155
|
+
model: z5.string().optional().describe(`Best-fit model override from the system prompt catalog. Omit agent+model to inherit ${ctx.agent}/${ctx.model ?? "default"}; overriding agent without model uses that agent's default.`)
|
|
21589
22156
|
};
|
|
21590
22157
|
return {
|
|
21591
22158
|
name: "spawn_subagent",
|
|
21592
22159
|
description: "Delegate a self-contained task to a subagent that works in its own new agent room. " + "Returns immediately; the subagent runs in the background and reports according to report_mode. " + "The fresh room has no parent conversation history; it receives the task plus the selected or inherited topic memory. " + "Use for parallelizable or long-running side work. Do not use provider built-in Task/Agent subagents.",
|
|
21593
22160
|
schema: {
|
|
21594
|
-
task:
|
|
21595
|
-
name:
|
|
22161
|
+
task: z5.string().describe("Self-contained task brief. The worker has topic memory but no parent conversation history, so include all task-specific context."),
|
|
22162
|
+
name: z5.string().optional().describe("Short name for the subagent room. Auto-generated when omitted."),
|
|
21596
22163
|
...executionOverrideSchema,
|
|
21597
|
-
memory_topic:
|
|
21598
|
-
report_mode:
|
|
22164
|
+
memory_topic: z5.string().optional().describe("Optional knowledge source: an accessible topic id/title or topic/*.md brief. Defaults to the parent room's effective topic brief."),
|
|
22165
|
+
report_mode: z5.enum(["auto", "tell", "status-only"]).optional().describe("Completion reporting: auto injects the final result, tell requires tell_session, status-only updates only the lifecycle card.")
|
|
21599
22166
|
},
|
|
21600
22167
|
async handler(input) {
|
|
21601
22168
|
return createSubagent(ctx, input, true);
|
|
@@ -21607,14 +22174,14 @@ ${card.task}${reportInstruction}`;
|
|
|
21607
22174
|
name: "create_subagent",
|
|
21608
22175
|
description: "Create a prepared subagent room and task definition without starting its AI turn. " + "Use start_subagent when the team is ready to run it.",
|
|
21609
22176
|
schema: {
|
|
21610
|
-
task:
|
|
21611
|
-
name:
|
|
22177
|
+
task: z5.string().describe("Self-contained task brief for the subagent."),
|
|
22178
|
+
name: z5.string().optional().describe("Short room name. Auto-generated when omitted."),
|
|
21612
22179
|
...options.executionOverrides === false ? {} : {
|
|
21613
|
-
agent:
|
|
21614
|
-
model:
|
|
22180
|
+
agent: z5.enum(["claude", "codex", "maestro"]).optional(),
|
|
22181
|
+
model: z5.string().optional()
|
|
21615
22182
|
},
|
|
21616
|
-
memory_topic:
|
|
21617
|
-
report_mode:
|
|
22183
|
+
memory_topic: z5.string().optional().describe("Optional accessible topic id/title or topic/*.md brief to use as this worker's knowledge source. Defaults to the parent."),
|
|
22184
|
+
report_mode: z5.enum(["auto", "tell", "status-only"]).optional()
|
|
21618
22185
|
},
|
|
21619
22186
|
async handler(input) {
|
|
21620
22187
|
return createSubagent(ctx, input, false);
|
|
@@ -22737,14 +23304,14 @@ await init_spawn_subagent();
|
|
|
22737
23304
|
|
|
22738
23305
|
// ../../packages/core/src/agents/mcp-tools/visuals.ts
|
|
22739
23306
|
init_common();
|
|
22740
|
-
import { z as
|
|
23307
|
+
import { z as z6 } from "zod";
|
|
22741
23308
|
var VISUALS_MCP_KEY = "visuals";
|
|
22742
23309
|
var showHtmlTool = {
|
|
22743
23310
|
name: "show_html",
|
|
22744
23311
|
description: "Display charts, tables, or interactive HTML to the user in a sandboxed WebView side panel. Pass a complete, self-contained HTML string with inline CSS/JS. Local DOM interactions are supported. Links (<a href> to http/https URLs) are safe to include: a click opens the URL in the user's system browser. Network requests, external scripts, form posts, popups, and parent-window access are blocked.",
|
|
22745
23312
|
schema: {
|
|
22746
|
-
html:
|
|
22747
|
-
title:
|
|
23313
|
+
html: z6.string().describe("Complete, self-contained HTML document or fragment to render."),
|
|
23314
|
+
title: z6.string().optional().describe("Optional title shown above the rendered card.")
|
|
22748
23315
|
},
|
|
22749
23316
|
async handler() {
|
|
22750
23317
|
return textResult("HTML card displayed to the user.");
|
|
@@ -22754,9 +23321,9 @@ var showMermaidTool = {
|
|
|
22754
23321
|
name: "show_mermaid",
|
|
22755
23322
|
description: "Render a Mermaid diagram in the user's visual side panel. Pass Mermaid DSL only, not markdown fences. Use this for flowcharts, sequence diagrams, class diagrams, state diagrams, ER diagrams, timelines, and architecture diagrams.",
|
|
22756
23323
|
schema: {
|
|
22757
|
-
code:
|
|
22758
|
-
title:
|
|
22759
|
-
theme:
|
|
23324
|
+
code: z6.string().describe("Mermaid diagram source without markdown code fences."),
|
|
23325
|
+
title: z6.string().optional().describe("Optional title shown above the rendered card."),
|
|
23326
|
+
theme: z6.enum(["default", "neutral", "dark", "forest"]).optional().describe("Optional Mermaid theme. Defaults to neutral.")
|
|
22760
23327
|
},
|
|
22761
23328
|
async handler() {
|
|
22762
23329
|
return textResult("Mermaid diagram displayed to the user.");
|
|
@@ -22766,10 +23333,10 @@ var showImageTool = {
|
|
|
22766
23333
|
name: "show_image",
|
|
22767
23334
|
description: "Display an image in the user's visual side panel. Provide either file_path for an image in the topic workspace, or file_id for an uploaded file already attached in this topic.",
|
|
22768
23335
|
schema: {
|
|
22769
|
-
file_path:
|
|
22770
|
-
file_id:
|
|
22771
|
-
title:
|
|
22772
|
-
alt:
|
|
23336
|
+
file_path: z6.string().optional().describe("Absolute or workspace-relative path to an image file."),
|
|
23337
|
+
file_id: z6.string().optional().describe("Full Otium uploaded file UUID for an image attached in this topic."),
|
|
23338
|
+
title: z6.string().optional().describe("Optional title shown above the rendered card."),
|
|
23339
|
+
alt: z6.string().optional().describe("Optional alt text for the image.")
|
|
22773
23340
|
},
|
|
22774
23341
|
async handler(input) {
|
|
22775
23342
|
if (!input?.file_path && !input?.file_id) {
|
|
@@ -22855,4 +23422,4 @@ export {
|
|
|
22855
23422
|
DEFAULT_SELF_CONFIG_PRODUCT
|
|
22856
23423
|
};
|
|
22857
23424
|
|
|
22858
|
-
//# debugId=
|
|
23425
|
+
//# debugId=EC76F8098D97C2B164756E2164756E21
|