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
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
|
|
@@ -3926,6 +3930,16 @@ function vaultListDecryptableValues(userId) {
|
|
|
3926
3930
|
}
|
|
3927
3931
|
return entries;
|
|
3928
3932
|
}
|
|
3933
|
+
function vaultSet(userId, key, value, description = "") {
|
|
3934
|
+
const normalizedKey = normalizeVaultKey(key);
|
|
3935
|
+
activeVaultDatabase().prepare(`INSERT INTO vault (user_id, key, value, description) VALUES (?, ?, ?, ?)
|
|
3936
|
+
ON CONFLICT (user_id, key) DO UPDATE
|
|
3937
|
+
SET value = excluded.value, description = excluded.description`).run(userId, normalizedKey, encryptVaultValue(userId, normalizedKey, value, vaultMasterKey), description);
|
|
3938
|
+
}
|
|
3939
|
+
function vaultHasKey(userId, key) {
|
|
3940
|
+
const row = activeVaultDatabase().prepare("SELECT 1 FROM vault WHERE user_id = ? AND key = ?").get(userId, normalizeVaultKey(key));
|
|
3941
|
+
return row != null;
|
|
3942
|
+
}
|
|
3929
3943
|
function vaultGetValue(userId, key) {
|
|
3930
3944
|
const normalizedKey = normalizeVaultKey(key);
|
|
3931
3945
|
const row = activeVaultDatabase().prepare("SELECT key, value FROM vault WHERE user_id = ? AND key = ?").get(userId, normalizedKey);
|
|
@@ -3984,7 +3998,7 @@ function redactVaultSecrets(userId, text) {
|
|
|
3984
3998
|
}
|
|
3985
3999
|
return redacted;
|
|
3986
4000
|
}
|
|
3987
|
-
var vaultDb, vaultMasterKey, VAULT_VALUE_MAX_BYTES;
|
|
4001
|
+
var vaultDb, vaultMasterKey, VAULT_KEY_PATTERN, VAULT_VALUE_MIN_BYTES = 4, VAULT_VALUE_MAX_BYTES, VAULT_DESCRIPTION_MAX_LENGTH = 500;
|
|
3988
4002
|
var init_vault = __esm(async () => {
|
|
3989
4003
|
init_config();
|
|
3990
4004
|
init_vault_crypto();
|
|
@@ -3993,6 +4007,7 @@ var init_vault = __esm(async () => {
|
|
|
3993
4007
|
init_storage_host()
|
|
3994
4008
|
]);
|
|
3995
4009
|
vaultMasterKey = VAULT_MASTER_KEY;
|
|
4010
|
+
VAULT_KEY_PATTERN = /^[A-Z][A-Z0-9_]{0,127}$/;
|
|
3996
4011
|
VAULT_VALUE_MAX_BYTES = 64 * 1024;
|
|
3997
4012
|
});
|
|
3998
4013
|
|
|
@@ -5215,7 +5230,7 @@ var init_codex = __esm(async () => {
|
|
|
5215
5230
|
});
|
|
5216
5231
|
|
|
5217
5232
|
// ../../packages/core/src/version.ts
|
|
5218
|
-
var NEGOTIUM_VERSION = "0.22.
|
|
5233
|
+
var NEGOTIUM_VERSION = "0.22.4";
|
|
5219
5234
|
|
|
5220
5235
|
// ../../packages/core/src/agents/codex-native-multi-agent.ts
|
|
5221
5236
|
import { spawn as spawn2 } from "child_process";
|
|
@@ -18305,6 +18320,548 @@ var init_topic_config = __esm(async () => {
|
|
|
18305
18320
|
await init_api_topic_config();
|
|
18306
18321
|
});
|
|
18307
18322
|
|
|
18323
|
+
// ../../packages/core/src/storage/vault-request-gates.ts
|
|
18324
|
+
function initializeVaultRequestGateSchema() {
|
|
18325
|
+
db.exec(`
|
|
18326
|
+
CREATE TABLE IF NOT EXISTS vault_request_gates (
|
|
18327
|
+
gate_id TEXT PRIMARY KEY,
|
|
18328
|
+
topic_id TEXT NOT NULL REFERENCES api_topics(id) ON DELETE CASCADE,
|
|
18329
|
+
query_id TEXT,
|
|
18330
|
+
idempotency_key TEXT NOT NULL,
|
|
18331
|
+
body_hash TEXT NOT NULL,
|
|
18332
|
+
message_id TEXT NOT NULL UNIQUE,
|
|
18333
|
+
owner_id TEXT NOT NULL,
|
|
18334
|
+
state TEXT NOT NULL,
|
|
18335
|
+
outcome TEXT,
|
|
18336
|
+
answered_by TEXT,
|
|
18337
|
+
claim_source TEXT,
|
|
18338
|
+
created_at TEXT NOT NULL,
|
|
18339
|
+
updated_at TEXT NOT NULL
|
|
18340
|
+
);
|
|
18341
|
+
CREATE INDEX IF NOT EXISTS idx_vault_request_gates_idempotency
|
|
18342
|
+
ON vault_request_gates(topic_id, idempotency_key, created_at);
|
|
18343
|
+
CREATE UNIQUE INDEX IF NOT EXISTS idx_vault_request_gates_active_key
|
|
18344
|
+
ON vault_request_gates(topic_id, idempotency_key)
|
|
18345
|
+
WHERE state IN ('pending', 'claimed');
|
|
18346
|
+
`);
|
|
18347
|
+
}
|
|
18348
|
+
function fromRow2(row) {
|
|
18349
|
+
return {
|
|
18350
|
+
gateId: row.gate_id,
|
|
18351
|
+
topicId: row.topic_id,
|
|
18352
|
+
queryId: row.query_id ?? undefined,
|
|
18353
|
+
idempotencyKey: row.idempotency_key,
|
|
18354
|
+
bodyHash: row.body_hash,
|
|
18355
|
+
messageId: row.message_id,
|
|
18356
|
+
ownerId: row.owner_id,
|
|
18357
|
+
state: row.state,
|
|
18358
|
+
outcome: row.outcome ?? undefined,
|
|
18359
|
+
answeredBy: row.answered_by ?? undefined
|
|
18360
|
+
};
|
|
18361
|
+
}
|
|
18362
|
+
function latestGate2(topicId, idempotencyKey) {
|
|
18363
|
+
const row = db.query(`SELECT * FROM vault_request_gates
|
|
18364
|
+
WHERE topic_id = ? AND idempotency_key = ?
|
|
18365
|
+
ORDER BY rowid DESC LIMIT 1`).get(topicId, idempotencyKey);
|
|
18366
|
+
return row ? fromRow2(row) : null;
|
|
18367
|
+
}
|
|
18368
|
+
function expireVaultCard(topicId, messageId, editedAt) {
|
|
18369
|
+
const row = db.query(`SELECT vault_secret_request FROM api_messages
|
|
18370
|
+
WHERE topic_id = ? AND id = ? AND deleted = 0 AND kind = 'vault_secret_request'`).get(topicId, messageId);
|
|
18371
|
+
if (!row?.vault_secret_request)
|
|
18372
|
+
return null;
|
|
18373
|
+
const request = JSON.parse(row.vault_secret_request);
|
|
18374
|
+
if (request.status !== "pending")
|
|
18375
|
+
return null;
|
|
18376
|
+
const expired = { ...request, status: "expired" };
|
|
18377
|
+
db.query(`UPDATE api_messages SET vault_secret_request = ?, edited_at = ?
|
|
18378
|
+
WHERE topic_id = ? AND id = ? AND deleted = 0 AND kind = 'vault_secret_request'`).run(JSON.stringify(expired), editedAt, topicId, messageId);
|
|
18379
|
+
return { topicId, messageId, vaultSecretRequest: expired, editedAt };
|
|
18380
|
+
}
|
|
18381
|
+
function prepareVaultRequestGate(args) {
|
|
18382
|
+
return db.transaction(() => {
|
|
18383
|
+
const latest = latestGate2(args.topicId, args.idempotencyKey);
|
|
18384
|
+
if (latest?.bodyHash !== undefined && latest.bodyHash !== args.bodyHash) {
|
|
18385
|
+
return { outcome: "conflict", gate: latest };
|
|
18386
|
+
}
|
|
18387
|
+
if (latest?.state === "answered")
|
|
18388
|
+
return { outcome: "replay", gate: latest };
|
|
18389
|
+
if (latest && (latest.state === "pending" || latest.state === "claimed")) {
|
|
18390
|
+
return { outcome: "pending", gate: latest };
|
|
18391
|
+
}
|
|
18392
|
+
db.query(`INSERT INTO vault_request_gates
|
|
18393
|
+
(gate_id, topic_id, query_id, idempotency_key, body_hash, message_id, owner_id, state, created_at, updated_at)
|
|
18394
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, 'pending', ?, ?)`).run(args.gateId, args.topicId, args.queryId ?? null, args.idempotencyKey, args.bodyHash, args.messageId, args.ownerId, args.now, args.now);
|
|
18395
|
+
return {
|
|
18396
|
+
outcome: "created",
|
|
18397
|
+
gate: {
|
|
18398
|
+
gateId: args.gateId,
|
|
18399
|
+
topicId: args.topicId,
|
|
18400
|
+
queryId: args.queryId,
|
|
18401
|
+
idempotencyKey: args.idempotencyKey,
|
|
18402
|
+
bodyHash: args.bodyHash,
|
|
18403
|
+
messageId: args.messageId,
|
|
18404
|
+
ownerId: args.ownerId,
|
|
18405
|
+
state: "pending"
|
|
18406
|
+
}
|
|
18407
|
+
};
|
|
18408
|
+
}).immediate();
|
|
18409
|
+
}
|
|
18410
|
+
function quarantineVaultRequestGate(gateId, ownerId, now = new Date().toISOString()) {
|
|
18411
|
+
return db.transaction(() => {
|
|
18412
|
+
const row = db.query("SELECT * FROM vault_request_gates WHERE gate_id = ?").get(gateId);
|
|
18413
|
+
if (!row || row.owner_id !== ownerId || !["pending", "claimed"].includes(row.state)) {
|
|
18414
|
+
return null;
|
|
18415
|
+
}
|
|
18416
|
+
const result = db.query(`UPDATE vault_request_gates SET state = 'quarantined', updated_at = ?
|
|
18417
|
+
WHERE gate_id = ? AND owner_id = ? AND state IN ('pending', 'claimed')`).run(now, gateId, ownerId);
|
|
18418
|
+
if (Number(result.changes ?? 0) === 0)
|
|
18419
|
+
return null;
|
|
18420
|
+
return expireVaultCard(row.topic_id, row.message_id, now);
|
|
18421
|
+
}).immediate();
|
|
18422
|
+
}
|
|
18423
|
+
function quarantineForeignVaultRequestGates(liveOwnerIds, now = new Date().toISOString()) {
|
|
18424
|
+
return db.transaction(() => {
|
|
18425
|
+
const rows = db.query(`SELECT * FROM vault_request_gates
|
|
18426
|
+
WHERE state IN ('pending', 'claimed')`).all();
|
|
18427
|
+
const updates = [];
|
|
18428
|
+
for (const row of rows) {
|
|
18429
|
+
if (liveOwnerIds.has(row.owner_id))
|
|
18430
|
+
continue;
|
|
18431
|
+
if (row.state === "claimed" && row.outcome && row.answered_by) {
|
|
18432
|
+
db.query(`UPDATE vault_request_gates SET state = 'answered', updated_at = ?
|
|
18433
|
+
WHERE gate_id = ? AND state = 'claimed'`).run(now, row.gate_id);
|
|
18434
|
+
continue;
|
|
18435
|
+
}
|
|
18436
|
+
const result = db.query(`UPDATE vault_request_gates SET state = 'quarantined', updated_at = ?
|
|
18437
|
+
WHERE gate_id = ? AND state IN ('pending', 'claimed')`).run(now, row.gate_id);
|
|
18438
|
+
if (Number(result.changes ?? 0) === 0)
|
|
18439
|
+
continue;
|
|
18440
|
+
const update = expireVaultCard(row.topic_id, row.message_id, now);
|
|
18441
|
+
if (update)
|
|
18442
|
+
updates.push(update);
|
|
18443
|
+
}
|
|
18444
|
+
const legacyCards = db.query(`SELECT m.topic_id, m.id
|
|
18445
|
+
FROM api_messages m
|
|
18446
|
+
LEFT JOIN vault_request_gates g ON g.message_id = m.id
|
|
18447
|
+
WHERE m.kind = 'vault_secret_request'
|
|
18448
|
+
AND m.deleted = 0
|
|
18449
|
+
AND g.gate_id IS NULL`).all();
|
|
18450
|
+
for (const card of legacyCards) {
|
|
18451
|
+
const update = expireVaultCard(card.topic_id, card.id, now);
|
|
18452
|
+
if (update)
|
|
18453
|
+
updates.push(update);
|
|
18454
|
+
}
|
|
18455
|
+
return updates;
|
|
18456
|
+
}).immediate();
|
|
18457
|
+
}
|
|
18458
|
+
function claimVaultRequestGateAndAnswer(args) {
|
|
18459
|
+
return db.transaction(() => {
|
|
18460
|
+
const row = db.query(`SELECT * FROM vault_request_gates
|
|
18461
|
+
WHERE topic_id = ? AND message_id = ?
|
|
18462
|
+
ORDER BY rowid DESC LIMIT 1`).get(args.topicId, args.messageId);
|
|
18463
|
+
if (!row || row.owner_id !== args.ownerId || row.state !== "pending") {
|
|
18464
|
+
return { outcome: "unavailable" };
|
|
18465
|
+
}
|
|
18466
|
+
const message = db.query(`SELECT vault_secret_request FROM api_messages
|
|
18467
|
+
WHERE topic_id = ? AND id = ? AND deleted = 0 AND kind = 'vault_secret_request'`).get(args.topicId, args.messageId);
|
|
18468
|
+
if (!message?.vault_secret_request)
|
|
18469
|
+
return { outcome: "unavailable" };
|
|
18470
|
+
const request = JSON.parse(message.vault_secret_request);
|
|
18471
|
+
if (request.status !== "pending")
|
|
18472
|
+
return { outcome: "unavailable" };
|
|
18473
|
+
const claimed = db.query(`UPDATE vault_request_gates
|
|
18474
|
+
SET state = 'answered', outcome = ?, answered_by = ?, claim_source = ?, updated_at = ?
|
|
18475
|
+
WHERE gate_id = ? AND owner_id = ? AND state = 'pending'`).run(args.outcome, args.userId, args.source, args.now, row.gate_id, args.ownerId);
|
|
18476
|
+
if (Number(claimed.changes ?? 0) === 0)
|
|
18477
|
+
return { outcome: "unavailable" };
|
|
18478
|
+
const answered = {
|
|
18479
|
+
...request,
|
|
18480
|
+
status: args.outcome,
|
|
18481
|
+
answeredBy: args.userId
|
|
18482
|
+
};
|
|
18483
|
+
db.query(`UPDATE api_messages SET vault_secret_request = ?, edited_at = ?
|
|
18484
|
+
WHERE topic_id = ? AND id = ? AND deleted = 0 AND kind = 'vault_secret_request'`).run(JSON.stringify(answered), args.now, args.topicId, args.messageId);
|
|
18485
|
+
return {
|
|
18486
|
+
outcome: "claimed",
|
|
18487
|
+
gate: fromRow2({
|
|
18488
|
+
...row,
|
|
18489
|
+
state: "answered",
|
|
18490
|
+
outcome: args.outcome,
|
|
18491
|
+
answered_by: args.userId
|
|
18492
|
+
}),
|
|
18493
|
+
vaultSecretRequest: answered,
|
|
18494
|
+
editedAt: args.now
|
|
18495
|
+
};
|
|
18496
|
+
}).immediate();
|
|
18497
|
+
}
|
|
18498
|
+
function cancelVaultRequestGate(topicId, messageId, ownerId, now = new Date().toISOString()) {
|
|
18499
|
+
return db.transaction(() => {
|
|
18500
|
+
const row = db.query(`SELECT * FROM vault_request_gates
|
|
18501
|
+
WHERE topic_id = ? AND message_id = ? ORDER BY rowid DESC LIMIT 1`).get(topicId, messageId);
|
|
18502
|
+
if (!row || row.owner_id !== ownerId || row.state !== "pending")
|
|
18503
|
+
return null;
|
|
18504
|
+
const result = db.query(`UPDATE vault_request_gates SET state = 'cancelled', updated_at = ?
|
|
18505
|
+
WHERE gate_id = ? AND owner_id = ? AND state = 'pending'`).run(now, row.gate_id, ownerId);
|
|
18506
|
+
if (Number(result.changes ?? 0) === 0)
|
|
18507
|
+
return null;
|
|
18508
|
+
return expireVaultCard(topicId, messageId, now);
|
|
18509
|
+
}).immediate();
|
|
18510
|
+
}
|
|
18511
|
+
var init_vault_request_gates = __esm(async () => {
|
|
18512
|
+
await __promiseAll([
|
|
18513
|
+
init_forum_db(),
|
|
18514
|
+
init_storage_host()
|
|
18515
|
+
]);
|
|
18516
|
+
registerStorageSchemaInitializer(initializeVaultRequestGateSchema, 32);
|
|
18517
|
+
});
|
|
18518
|
+
|
|
18519
|
+
// ../../packages/core/src/agents/mcp-tools/request-vault.ts
|
|
18520
|
+
import { createHash as createHash12, randomUUID as randomUUID17 } from "crypto";
|
|
18521
|
+
import { z as z4 } from "zod";
|
|
18522
|
+
function requestBodyHash(key, description) {
|
|
18523
|
+
return createHash12("sha256").update(JSON.stringify({ key, description })).digest("hex");
|
|
18524
|
+
}
|
|
18525
|
+
function answerToolResult2(key, outcome) {
|
|
18526
|
+
if (outcome === "no_answer") {
|
|
18527
|
+
return errorResult("The AI turn ended before the user answered.");
|
|
18528
|
+
}
|
|
18529
|
+
if (outcome === "declined") {
|
|
18530
|
+
return errorResult(`User declined to provide a value for ${key}.`);
|
|
18531
|
+
}
|
|
18532
|
+
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.`);
|
|
18533
|
+
}
|
|
18534
|
+
function createRequestVaultRuntime(host) {
|
|
18535
|
+
const pendingRequests = new Map;
|
|
18536
|
+
const { ownerId } = host.runtime;
|
|
18537
|
+
const createId = () => host.runtime.createId();
|
|
18538
|
+
const now = () => host.runtime.now();
|
|
18539
|
+
const isProcessLeaseAlive = (lease) => host.durability.processLeases.isAlive(lease);
|
|
18540
|
+
let gateOwnerLease = null;
|
|
18541
|
+
function broadcastGateUpdates(updates) {
|
|
18542
|
+
for (const update of updates)
|
|
18543
|
+
host.messaging.publication.publishCardUpdate(update);
|
|
18544
|
+
}
|
|
18545
|
+
function startGateOwner() {
|
|
18546
|
+
if (gateOwnerLease)
|
|
18547
|
+
return;
|
|
18548
|
+
gateOwnerLease = host.durability.processLeases.acquire(`${VAULT_GATE_LEASE_ROLE_PREFIX}${ownerId}`, ownerId);
|
|
18549
|
+
if (!gateOwnerLease)
|
|
18550
|
+
throw new Error("failed to acquire the vault-request gate owner lease");
|
|
18551
|
+
}
|
|
18552
|
+
function stopGateOwner() {
|
|
18553
|
+
gateOwnerLease?.stop();
|
|
18554
|
+
gateOwnerLease = null;
|
|
18555
|
+
}
|
|
18556
|
+
function reconcilePendingGates() {
|
|
18557
|
+
startGateOwner();
|
|
18558
|
+
host.durability.processLeases.removeDead(VAULT_GATE_LEASE_ROLE_PREFIX);
|
|
18559
|
+
const liveOwners = new Set(host.durability.processLeases.list(VAULT_GATE_LEASE_ROLE_PREFIX).filter(isProcessLeaseAlive).map((lease) => lease.ownerId));
|
|
18560
|
+
const updates = host.durability.gates.quarantineForeign(liveOwners, now());
|
|
18561
|
+
broadcastGateUpdates(updates);
|
|
18562
|
+
return updates.length;
|
|
18563
|
+
}
|
|
18564
|
+
function answerPendingRequest(topicId, messageId, userId, value) {
|
|
18565
|
+
reconcilePendingGates();
|
|
18566
|
+
const topic = host.topics.getTopic(topicId);
|
|
18567
|
+
if (!topic?.participants.some((participant) => participant.userId === userId)) {
|
|
18568
|
+
return { ok: false, error: "User is not a member of this topic." };
|
|
18569
|
+
}
|
|
18570
|
+
const existing = host.messaging.persistence.getMessage(topicId, messageId);
|
|
18571
|
+
const request = existing?.kind === "vault_secret_request" ? existing.vaultSecretRequest : undefined;
|
|
18572
|
+
if (!existing || existing.deleted || !request) {
|
|
18573
|
+
return { ok: false, error: "Vault request card not found." };
|
|
18574
|
+
}
|
|
18575
|
+
if (request.status !== "pending") {
|
|
18576
|
+
return { ok: false, error: "This request is no longer awaiting an answer." };
|
|
18577
|
+
}
|
|
18578
|
+
const pending = pendingRequests.get(messageId);
|
|
18579
|
+
if (!pending || pending.topicId !== topicId) {
|
|
18580
|
+
return { ok: false, error: "This request is no longer awaiting an answer." };
|
|
18581
|
+
}
|
|
18582
|
+
const declining = value === null;
|
|
18583
|
+
if (!declining) {
|
|
18584
|
+
const bytes = Buffer.byteLength(value, "utf8");
|
|
18585
|
+
if (bytes < VAULT_VALUE_MIN_BYTES || bytes > VAULT_VALUE_MAX_BYTES) {
|
|
18586
|
+
return {
|
|
18587
|
+
ok: false,
|
|
18588
|
+
error: `Value must be between ${VAULT_VALUE_MIN_BYTES} and ${VAULT_VALUE_MAX_BYTES} bytes.`
|
|
18589
|
+
};
|
|
18590
|
+
}
|
|
18591
|
+
}
|
|
18592
|
+
if (!declining) {
|
|
18593
|
+
try {
|
|
18594
|
+
host.vault.set(userId, pending.key, value, pending.description);
|
|
18595
|
+
} catch (error) {
|
|
18596
|
+
return {
|
|
18597
|
+
ok: false,
|
|
18598
|
+
error: `Failed to store the value: ${error instanceof Error ? error.message : String(error)}`
|
|
18599
|
+
};
|
|
18600
|
+
}
|
|
18601
|
+
}
|
|
18602
|
+
const outcome = declining ? "declined" : "fulfilled";
|
|
18603
|
+
let claim;
|
|
18604
|
+
try {
|
|
18605
|
+
claim = host.durability.gates.claimAndAnswer({
|
|
18606
|
+
topicId,
|
|
18607
|
+
messageId,
|
|
18608
|
+
outcome,
|
|
18609
|
+
userId,
|
|
18610
|
+
ownerId,
|
|
18611
|
+
source: "host-control",
|
|
18612
|
+
now: now()
|
|
18613
|
+
});
|
|
18614
|
+
} catch {
|
|
18615
|
+
return { ok: false, error: "This request could not be claimed safely. Retry once." };
|
|
18616
|
+
}
|
|
18617
|
+
if (claim.outcome !== "claimed") {
|
|
18618
|
+
return { ok: false, error: "This request is no longer awaiting an answer." };
|
|
18619
|
+
}
|
|
18620
|
+
pendingRequests.delete(messageId);
|
|
18621
|
+
const answerMessage = {
|
|
18622
|
+
id: createId(),
|
|
18623
|
+
topicId,
|
|
18624
|
+
authorId: userId,
|
|
18625
|
+
text: outcome === "fulfilled" ? `Provided a value for ${pending.key}.` : `Declined to provide a value for ${pending.key}.`,
|
|
18626
|
+
parentId: messageId,
|
|
18627
|
+
...existing.threadRootId ? { threadRootId: existing.threadRootId } : {},
|
|
18628
|
+
createdAt: now()
|
|
18629
|
+
};
|
|
18630
|
+
try {
|
|
18631
|
+
host.messaging.publication.publishCardUpdate({
|
|
18632
|
+
topicId,
|
|
18633
|
+
messageId,
|
|
18634
|
+
vaultSecretRequest: claim.vaultSecretRequest,
|
|
18635
|
+
editedAt: claim.editedAt
|
|
18636
|
+
});
|
|
18637
|
+
host.messaging.persistence.appendMessage(answerMessage);
|
|
18638
|
+
host.messaging.publication.publishMessage(topicId, answerMessage);
|
|
18639
|
+
} catch {}
|
|
18640
|
+
pending.resolve({ outcome, userId });
|
|
18641
|
+
return { ok: true, queryId: pending.queryId, outcome, answerMessage };
|
|
18642
|
+
}
|
|
18643
|
+
function cancelPendingRequests(topicId, queryId) {
|
|
18644
|
+
for (const [messageId, pending] of pendingRequests) {
|
|
18645
|
+
if (pending.topicId === topicId && pending.queryId === queryId) {
|
|
18646
|
+
pendingRequests.delete(messageId);
|
|
18647
|
+
const update = host.durability.gates.cancel(topicId, messageId, ownerId, now());
|
|
18648
|
+
if (update)
|
|
18649
|
+
broadcastGateUpdates([update]);
|
|
18650
|
+
pending.resolve({ outcome: "no_answer", userId: "" });
|
|
18651
|
+
}
|
|
18652
|
+
}
|
|
18653
|
+
}
|
|
18654
|
+
function appendRequestMessage(ctx, key, description, messageId) {
|
|
18655
|
+
const cfg = host.topics.getConfig(ctx.topicId);
|
|
18656
|
+
const vaultSecretRequest = { key, description, status: "pending" };
|
|
18657
|
+
const message = {
|
|
18658
|
+
id: messageId,
|
|
18659
|
+
topicId: ctx.topicId,
|
|
18660
|
+
authorId: "ai",
|
|
18661
|
+
text: `Vault secret requested: ${key}`,
|
|
18662
|
+
queryId: ctx.queryId,
|
|
18663
|
+
agentType: ctx.agent,
|
|
18664
|
+
model: ctx.model ?? cfg?.model ?? "unknown",
|
|
18665
|
+
kind: "vault_secret_request",
|
|
18666
|
+
vaultSecretRequest,
|
|
18667
|
+
...ctx.threadRootId ? { threadRootId: ctx.threadRootId } : {},
|
|
18668
|
+
createdAt: now()
|
|
18669
|
+
};
|
|
18670
|
+
host.messaging.persistence.appendMessage(message);
|
|
18671
|
+
return message;
|
|
18672
|
+
}
|
|
18673
|
+
async function requestVaultSecret(ctx, input) {
|
|
18674
|
+
if (ctx.unsupportedSurface) {
|
|
18675
|
+
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.");
|
|
18676
|
+
}
|
|
18677
|
+
reconcilePendingGates();
|
|
18678
|
+
const topic = host.topics.getTopic(ctx.topicId);
|
|
18679
|
+
if (!topic)
|
|
18680
|
+
return errorResult(`Error: topic '${ctx.topicId}' not found.`);
|
|
18681
|
+
if (!topic.participants.some((participant) => participant.userId === ctx.userId)) {
|
|
18682
|
+
return errorResult("Error: user is not a member of this topic.");
|
|
18683
|
+
}
|
|
18684
|
+
if (typeof input.key !== "string" || !input.key.trim()) {
|
|
18685
|
+
return errorResult("Error: key is required.");
|
|
18686
|
+
}
|
|
18687
|
+
const key = normalizeVaultKey(input.key);
|
|
18688
|
+
if (!VAULT_KEY_PATTERN.test(key)) {
|
|
18689
|
+
return errorResult("Error: key must start with a letter and contain only uppercase letters, digits, and underscores (max 128 chars).");
|
|
18690
|
+
}
|
|
18691
|
+
if (typeof input.description !== "string" || !input.description.trim()) {
|
|
18692
|
+
return errorResult("Error: description is required so the user knows what to type.");
|
|
18693
|
+
}
|
|
18694
|
+
const description = input.description.trim().slice(0, MAX_DESCRIPTION_CHARS);
|
|
18695
|
+
if (input.idempotency_key !== undefined && (typeof input.idempotency_key !== "string" || !input.idempotency_key.trim())) {
|
|
18696
|
+
return errorResult("Error: idempotency_key must be a non-empty string.");
|
|
18697
|
+
}
|
|
18698
|
+
const rawIdempotencyKey = typeof input.idempotency_key === "string" ? input.idempotency_key.trim() : createId();
|
|
18699
|
+
if (rawIdempotencyKey.length > MAX_IDEMPOTENCY_KEY_CHARS2) {
|
|
18700
|
+
return errorResult(`Error: idempotency_key must be at most ${MAX_IDEMPOTENCY_KEY_CHARS2} characters.`);
|
|
18701
|
+
}
|
|
18702
|
+
const bodyHash = requestBodyHash(key, description);
|
|
18703
|
+
const createGate = () => {
|
|
18704
|
+
const suffix = createId();
|
|
18705
|
+
return host.durability.gates.prepare({
|
|
18706
|
+
gateId: `vault-request-gate-${suffix}`,
|
|
18707
|
+
topicId: ctx.topicId,
|
|
18708
|
+
queryId: ctx.queryId,
|
|
18709
|
+
idempotencyKey: rawIdempotencyKey,
|
|
18710
|
+
bodyHash,
|
|
18711
|
+
messageId: `vault-request-${ctx.queryId ?? "runtime"}-${suffix}`,
|
|
18712
|
+
ownerId,
|
|
18713
|
+
now: now()
|
|
18714
|
+
});
|
|
18715
|
+
};
|
|
18716
|
+
let prepared = createGate();
|
|
18717
|
+
if (prepared.outcome === "conflict") {
|
|
18718
|
+
return errorResult("Error: idempotency_conflict. The same idempotency_key was already used with a different key or description.");
|
|
18719
|
+
}
|
|
18720
|
+
if (prepared.outcome === "replay") {
|
|
18721
|
+
if (!prepared.gate.outcome || !prepared.gate.answeredBy) {
|
|
18722
|
+
return errorResult("Error: the stored request_vault_secret replay is incomplete.");
|
|
18723
|
+
}
|
|
18724
|
+
return answerToolResult2(key, prepared.gate.outcome);
|
|
18725
|
+
}
|
|
18726
|
+
if (prepared.outcome === "pending") {
|
|
18727
|
+
if (prepared.gate.ownerId !== ownerId) {
|
|
18728
|
+
return errorResult("Error: this request_vault_secret is pending in another healthy runtime process.");
|
|
18729
|
+
}
|
|
18730
|
+
const existingPending = pendingRequests.get(prepared.gate.messageId);
|
|
18731
|
+
if (existingPending)
|
|
18732
|
+
return answerToolResult2(key, (await existingPending.promise).outcome);
|
|
18733
|
+
if (prepared.gate.state === "claimed") {
|
|
18734
|
+
return errorResult("Error: this request_vault_secret is already being finalized.");
|
|
18735
|
+
}
|
|
18736
|
+
const update = host.durability.gates.quarantine(prepared.gate.gateId, ownerId, now());
|
|
18737
|
+
if (update)
|
|
18738
|
+
broadcastGateUpdates([update]);
|
|
18739
|
+
prepared = createGate();
|
|
18740
|
+
}
|
|
18741
|
+
if (prepared.outcome !== "created") {
|
|
18742
|
+
return errorResult("Error: failed to create a durable request_vault_secret gate.");
|
|
18743
|
+
}
|
|
18744
|
+
if (host.vault.hasKey(ctx.userId, key)) {
|
|
18745
|
+
host.durability.gates.quarantine(prepared.gate.gateId, ownerId, now());
|
|
18746
|
+
return errorResult(`Error: vault key '${key}' already exists. Pick a different key, or ask the user to update it from their Vault manager.`);
|
|
18747
|
+
}
|
|
18748
|
+
let message;
|
|
18749
|
+
try {
|
|
18750
|
+
message = appendRequestMessage(ctx, key, description, prepared.gate.messageId);
|
|
18751
|
+
} catch (error) {
|
|
18752
|
+
host.durability.gates.quarantine(prepared.gate.gateId, ownerId, now());
|
|
18753
|
+
return errorResult(`Error: failed to persist request_vault_secret: ${error instanceof Error ? error.message : String(error)}`);
|
|
18754
|
+
}
|
|
18755
|
+
let resolveAnswer;
|
|
18756
|
+
const promise = new Promise((resolve17) => {
|
|
18757
|
+
resolveAnswer = resolve17;
|
|
18758
|
+
});
|
|
18759
|
+
pendingRequests.set(message.id, {
|
|
18760
|
+
topicId: ctx.topicId,
|
|
18761
|
+
queryId: ctx.queryId,
|
|
18762
|
+
messageId: message.id,
|
|
18763
|
+
key,
|
|
18764
|
+
description,
|
|
18765
|
+
promise,
|
|
18766
|
+
resolve: resolveAnswer
|
|
18767
|
+
});
|
|
18768
|
+
try {
|
|
18769
|
+
host.messaging.publication.publishMessage(ctx.topicId, message);
|
|
18770
|
+
} catch (error) {
|
|
18771
|
+
pendingRequests.delete(message.id);
|
|
18772
|
+
try {
|
|
18773
|
+
host.durability.gates.quarantine(prepared.gate.gateId, ownerId, now());
|
|
18774
|
+
} catch {}
|
|
18775
|
+
return errorResult(`Error: failed to publish request_vault_secret: ${error instanceof Error ? error.message : String(error)}`);
|
|
18776
|
+
}
|
|
18777
|
+
return answerToolResult2(key, (await promise).outcome);
|
|
18778
|
+
}
|
|
18779
|
+
function createToolDefinition(ctx) {
|
|
18780
|
+
return {
|
|
18781
|
+
name: "request_vault_secret",
|
|
18782
|
+
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.",
|
|
18783
|
+
schema: {
|
|
18784
|
+
key: z4.string().describe("New Vault key name, e.g. STRIPE_API_KEY. Letters/digits/underscores, must start with a letter."),
|
|
18785
|
+
description: z4.string().max(MAX_DESCRIPTION_CHARS).describe("One sentence telling the user what value to paste and where it's from."),
|
|
18786
|
+
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.")
|
|
18787
|
+
},
|
|
18788
|
+
async handler(input) {
|
|
18789
|
+
return requestVaultSecret(ctx, input);
|
|
18790
|
+
}
|
|
18791
|
+
};
|
|
18792
|
+
}
|
|
18793
|
+
return {
|
|
18794
|
+
startGateOwner,
|
|
18795
|
+
stopGateOwner,
|
|
18796
|
+
reconcilePendingGates,
|
|
18797
|
+
answerPendingRequest,
|
|
18798
|
+
cancelPendingRequests,
|
|
18799
|
+
createToolDefinition
|
|
18800
|
+
};
|
|
18801
|
+
}
|
|
18802
|
+
function cancelPendingVaultSecretRequests(topicId, queryId) {
|
|
18803
|
+
defaultRequestVaultRuntime.cancelPendingRequests(topicId, queryId);
|
|
18804
|
+
}
|
|
18805
|
+
var MAX_DESCRIPTION_CHARS, MAX_IDEMPOTENCY_KEY_CHARS2 = 200, VAULT_GATE_OWNER_ID, VAULT_GATE_LEASE_ROLE_PREFIX = "vault-request-gate:", defaultRequestVaultDurabilityHost, defaultRequestVaultRuntime;
|
|
18806
|
+
var init_request_vault = __esm(async () => {
|
|
18807
|
+
init_common();
|
|
18808
|
+
await __promiseAll([
|
|
18809
|
+
init_bus(),
|
|
18810
|
+
init_api_messages(),
|
|
18811
|
+
init_api_topic_config(),
|
|
18812
|
+
init_api_topics(),
|
|
18813
|
+
init_runtime_process_leases(),
|
|
18814
|
+
init_vault(),
|
|
18815
|
+
init_vault_request_gates()
|
|
18816
|
+
]);
|
|
18817
|
+
MAX_DESCRIPTION_CHARS = VAULT_DESCRIPTION_MAX_LENGTH;
|
|
18818
|
+
VAULT_GATE_OWNER_ID = `request-vault-${process.pid}-${randomUUID17()}`;
|
|
18819
|
+
defaultRequestVaultDurabilityHost = {
|
|
18820
|
+
gates: {
|
|
18821
|
+
prepare: prepareVaultRequestGate,
|
|
18822
|
+
claimAndAnswer: claimVaultRequestGateAndAnswer,
|
|
18823
|
+
cancel: cancelVaultRequestGate,
|
|
18824
|
+
quarantine: quarantineVaultRequestGate,
|
|
18825
|
+
quarantineForeign: quarantineForeignVaultRequestGates
|
|
18826
|
+
},
|
|
18827
|
+
processLeases: {
|
|
18828
|
+
acquire: (role, ownerId) => acquireRuntimeProcessLease(role, { ownerId }),
|
|
18829
|
+
list: listRuntimeProcessLeases,
|
|
18830
|
+
isAlive: isRuntimeProcessLeaseAlive,
|
|
18831
|
+
removeDead: removeDeadRuntimeProcessLeases
|
|
18832
|
+
}
|
|
18833
|
+
};
|
|
18834
|
+
defaultRequestVaultRuntime = createRequestVaultRuntime({
|
|
18835
|
+
messaging: {
|
|
18836
|
+
persistence: {
|
|
18837
|
+
getMessage: getApiMessage,
|
|
18838
|
+
appendMessage: appendApiMessage
|
|
18839
|
+
},
|
|
18840
|
+
publication: {
|
|
18841
|
+
publishMessage: (topicId, message) => WsHub.get().broadcastMessage(topicId, message),
|
|
18842
|
+
publishCardUpdate: (update) => WsHub.get().broadcastMessageUpdated(update.topicId, update.messageId, {
|
|
18843
|
+
vaultSecretRequest: update.vaultSecretRequest,
|
|
18844
|
+
editedAt: update.editedAt
|
|
18845
|
+
})
|
|
18846
|
+
}
|
|
18847
|
+
},
|
|
18848
|
+
topics: {
|
|
18849
|
+
getTopic,
|
|
18850
|
+
getConfig: getApiTopicConfig
|
|
18851
|
+
},
|
|
18852
|
+
vault: {
|
|
18853
|
+
hasKey: vaultHasKey,
|
|
18854
|
+
set: vaultSet
|
|
18855
|
+
},
|
|
18856
|
+
durability: defaultRequestVaultDurabilityHost,
|
|
18857
|
+
runtime: {
|
|
18858
|
+
ownerId: VAULT_GATE_OWNER_ID,
|
|
18859
|
+
createId: randomUUID17,
|
|
18860
|
+
now: () => new Date().toISOString()
|
|
18861
|
+
}
|
|
18862
|
+
});
|
|
18863
|
+
});
|
|
18864
|
+
|
|
18308
18865
|
// ../../packages/core/src/mcp/peer-bridge.ts
|
|
18309
18866
|
function flushPeerRuntimeEvents(localTopicId) {
|
|
18310
18867
|
return activeBridge2?.flushEvents?.(localTopicId) ?? Promise.resolve(true);
|
|
@@ -18679,7 +19236,7 @@ var init_visuals = __esm(async () => {
|
|
|
18679
19236
|
});
|
|
18680
19237
|
|
|
18681
19238
|
// ../../packages/core/src/runtime/turn-event-stream.ts
|
|
18682
|
-
import { randomUUID as
|
|
19239
|
+
import { randomUUID as randomUUID18 } from "crypto";
|
|
18683
19240
|
import { realpathSync as realpathSync6, statSync as statSync9 } from "fs";
|
|
18684
19241
|
import { isAbsolute as isAbsolute7, resolve as resolve18 } from "path";
|
|
18685
19242
|
function sessionEventMatchesCurrentExecution(topicId, queryId, agent, model) {
|
|
@@ -18786,7 +19343,7 @@ async function runTurnEventStream(topicId, topicTitle, queryId, events, control,
|
|
|
18786
19343
|
if (silent || !text2.trim())
|
|
18787
19344
|
return null;
|
|
18788
19345
|
const message = {
|
|
18789
|
-
id:
|
|
19346
|
+
id: randomUUID18(),
|
|
18790
19347
|
topicId,
|
|
18791
19348
|
authorId: "ai",
|
|
18792
19349
|
text: text2,
|
|
@@ -19266,6 +19823,7 @@ ${JSON.stringify(event.input ?? {})}`);
|
|
|
19266
19823
|
const stillCurrent = getRoomQuery(roomId)?.queryId === queryId;
|
|
19267
19824
|
clearRoomQuery(roomId, queryId);
|
|
19268
19825
|
cancelPendingAskUserQuestions(topicId, queryId);
|
|
19826
|
+
cancelPendingVaultSecretRequests(topicId, queryId);
|
|
19269
19827
|
if (stillCurrent && roomId === topicId)
|
|
19270
19828
|
clearQueryState(userId, topicId, topicTitle);
|
|
19271
19829
|
if (!silent && stillCurrent)
|
|
@@ -19309,6 +19867,7 @@ var init_turn_event_stream = __esm(async () => {
|
|
|
19309
19867
|
init_idle_archiver(),
|
|
19310
19868
|
init_idle_compact(),
|
|
19311
19869
|
init_ask_user(),
|
|
19870
|
+
init_request_vault(),
|
|
19312
19871
|
init_spawn_subagent(),
|
|
19313
19872
|
init_registry(),
|
|
19314
19873
|
init_bus(),
|
|
@@ -19463,7 +20022,7 @@ __export(exports_turn_runner, {
|
|
|
19463
20022
|
FALLBACK_ORDER: () => FALLBACK_ORDER,
|
|
19464
20023
|
AGENT_DISPLAY_NAME: () => AGENT_DISPLAY_NAME
|
|
19465
20024
|
});
|
|
19466
|
-
import { randomUUID as
|
|
20025
|
+
import { randomUUID as randomUUID19 } from "crypto";
|
|
19467
20026
|
import { existsSync as existsSync22, mkdirSync as mkdirSync19, readdirSync as readdirSync5, statSync as statSync10 } from "fs";
|
|
19468
20027
|
import { join as join31 } from "path";
|
|
19469
20028
|
function withDefaultPlaywright(configuredMcp, isManager) {
|
|
@@ -19488,7 +20047,7 @@ ${reminderPrompt}` };
|
|
|
19488
20047
|
}
|
|
19489
20048
|
function appendSystemMessage(topicId, text2, threadRootId) {
|
|
19490
20049
|
const message = {
|
|
19491
|
-
id:
|
|
20050
|
+
id: randomUUID19(),
|
|
19492
20051
|
topicId,
|
|
19493
20052
|
authorId: "system",
|
|
19494
20053
|
text: text2,
|
|
@@ -19510,7 +20069,7 @@ function notifyPlaywrightUnavailable(topicId) {
|
|
|
19510
20069
|
}
|
|
19511
20070
|
function appendAskReplyMessage(topicId, text2, sourceLabel, body, kind, agentType, threadRootId) {
|
|
19512
20071
|
const message = {
|
|
19513
|
-
id:
|
|
20072
|
+
id: randomUUID19(),
|
|
19514
20073
|
topicId,
|
|
19515
20074
|
authorId: "ai",
|
|
19516
20075
|
text: text2,
|
|
@@ -20031,7 +20590,7 @@ function startAiTurn(params) {
|
|
|
20031
20590
|
const askReplySources = params.askReplySources;
|
|
20032
20591
|
const sessionRetried = params._sessionRetried === true;
|
|
20033
20592
|
const emptyResponseRetried = params._emptyResponseRetried === true;
|
|
20034
|
-
const queryId = asTurnId(params._queryId ??
|
|
20593
|
+
const queryId = asTurnId(params._queryId ?? randomUUID19());
|
|
20035
20594
|
const roomId = turnConcurrency === "isolated" ? isolatedTurnRoomId(topicId, queryId) : asTurnSlotKey(topicId);
|
|
20036
20595
|
const currentRuntimeEpoch = getRuntimeTopicEpoch(topic.id);
|
|
20037
20596
|
const runtimeEpoch = params._runtimeEpoch ?? currentRuntimeEpoch;
|
|
@@ -20802,7 +21361,7 @@ function triggerTopicAiTurn(topicId, userId, prompt, agentType, opts) {
|
|
|
20802
21361
|
if (!opts?.silent && !opts?.hideInjectMessage) {
|
|
20803
21362
|
const now = new Date().toISOString();
|
|
20804
21363
|
const injectMsg = {
|
|
20805
|
-
id: opts?.requestId ? `tell-${opts.requestId}` : `tell-${
|
|
21364
|
+
id: opts?.requestId ? `tell-${opts.requestId}` : `tell-${randomUUID19()}`,
|
|
20806
21365
|
topicId,
|
|
20807
21366
|
authorId: opts?.injectAuthorId ?? userId,
|
|
20808
21367
|
sourceAdapter: opts?.injectSourceAdapter,
|
|
@@ -20924,7 +21483,7 @@ var init_turn_runner = __esm(async () => {
|
|
|
20924
21483
|
});
|
|
20925
21484
|
|
|
20926
21485
|
// ../../packages/core/src/agents/mcp-tools/spawn-subagent.ts
|
|
20927
|
-
import { z as
|
|
21486
|
+
import { z as z5 } from "zod";
|
|
20928
21487
|
function activeQueryUserId(topic) {
|
|
20929
21488
|
return topic?.participants.find((participant) => hasActiveQuery(participant.userId, topic.id))?.userId;
|
|
20930
21489
|
}
|
|
@@ -21491,7 +22050,7 @@ ${card.task}${reportInstruction}`;
|
|
|
21491
22050
|
name: "start_subagent",
|
|
21492
22051
|
description: "Start a prepared descendant subagent task.",
|
|
21493
22052
|
schema: {
|
|
21494
|
-
topic_id:
|
|
22053
|
+
topic_id: z5.string().describe("Prepared subagent room id.")
|
|
21495
22054
|
},
|
|
21496
22055
|
async handler(input) {
|
|
21497
22056
|
const result = ownedSubagentTree(ctx);
|
|
@@ -21511,7 +22070,7 @@ ${card.task}${reportInstruction}`;
|
|
|
21511
22070
|
name: "delete_subagent",
|
|
21512
22071
|
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
22072
|
schema: {
|
|
21514
|
-
topic_id:
|
|
22073
|
+
topic_id: z5.string().describe("Exact subagent room id returned by list_subagents; names are not accepted.")
|
|
21515
22074
|
},
|
|
21516
22075
|
async handler(input) {
|
|
21517
22076
|
const result = ownedSubagentTree(ctx);
|
|
@@ -21535,8 +22094,8 @@ ${card.task}${reportInstruction}`;
|
|
|
21535
22094
|
name: "grant_subagent_tell",
|
|
21536
22095
|
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
22096
|
schema: {
|
|
21538
|
-
subagent_topic_id:
|
|
21539
|
-
target_topic_id:
|
|
22097
|
+
subagent_topic_id: z5.string().describe("Source descendant subagent topic id."),
|
|
22098
|
+
target_topic_id: z5.string().describe("Target topic id: this manager room or another descendant in its tree.")
|
|
21540
22099
|
},
|
|
21541
22100
|
async handler(input) {
|
|
21542
22101
|
const result = ownedSubagentTree(ctx);
|
|
@@ -21561,8 +22120,8 @@ ${card.task}${reportInstruction}`;
|
|
|
21561
22120
|
name: "revoke_subagent_tell",
|
|
21562
22121
|
description: "Remove an extra tell_session connection previously granted to a descendant subagent.",
|
|
21563
22122
|
schema: {
|
|
21564
|
-
subagent_topic_id:
|
|
21565
|
-
target_topic_id:
|
|
22123
|
+
subagent_topic_id: z5.string().describe("Source descendant subagent topic id."),
|
|
22124
|
+
target_topic_id: z5.string().describe("Granted target topic id.")
|
|
21566
22125
|
},
|
|
21567
22126
|
async handler(input) {
|
|
21568
22127
|
const result = ownedSubagentTree(ctx);
|
|
@@ -21584,18 +22143,18 @@ ${card.task}${reportInstruction}`;
|
|
|
21584
22143
|
}
|
|
21585
22144
|
function createSpawnSubagentToolDefinition(ctx, options = {}) {
|
|
21586
22145
|
const executionOverrideSchema = options.executionOverrides === false ? {} : {
|
|
21587
|
-
agent:
|
|
21588
|
-
model:
|
|
22146
|
+
agent: z5.enum(["claude", "codex", "maestro"]).optional().describe("Agent backend override. Defaults to this room's agent."),
|
|
22147
|
+
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
22148
|
};
|
|
21590
22149
|
return {
|
|
21591
22150
|
name: "spawn_subagent",
|
|
21592
22151
|
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
22152
|
schema: {
|
|
21594
|
-
task:
|
|
21595
|
-
name:
|
|
22153
|
+
task: z5.string().describe("Self-contained task brief. The worker has topic memory but no parent conversation history, so include all task-specific context."),
|
|
22154
|
+
name: z5.string().optional().describe("Short name for the subagent room. Auto-generated when omitted."),
|
|
21596
22155
|
...executionOverrideSchema,
|
|
21597
|
-
memory_topic:
|
|
21598
|
-
report_mode:
|
|
22156
|
+
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."),
|
|
22157
|
+
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
22158
|
},
|
|
21600
22159
|
async handler(input) {
|
|
21601
22160
|
return createSubagent(ctx, input, true);
|
|
@@ -21607,14 +22166,14 @@ ${card.task}${reportInstruction}`;
|
|
|
21607
22166
|
name: "create_subagent",
|
|
21608
22167
|
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
22168
|
schema: {
|
|
21610
|
-
task:
|
|
21611
|
-
name:
|
|
22169
|
+
task: z5.string().describe("Self-contained task brief for the subagent."),
|
|
22170
|
+
name: z5.string().optional().describe("Short room name. Auto-generated when omitted."),
|
|
21612
22171
|
...options.executionOverrides === false ? {} : {
|
|
21613
|
-
agent:
|
|
21614
|
-
model:
|
|
22172
|
+
agent: z5.enum(["claude", "codex", "maestro"]).optional(),
|
|
22173
|
+
model: z5.string().optional()
|
|
21615
22174
|
},
|
|
21616
|
-
memory_topic:
|
|
21617
|
-
report_mode:
|
|
22175
|
+
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."),
|
|
22176
|
+
report_mode: z5.enum(["auto", "tell", "status-only"]).optional()
|
|
21618
22177
|
},
|
|
21619
22178
|
async handler(input) {
|
|
21620
22179
|
return createSubagent(ctx, input, false);
|
|
@@ -22737,14 +23296,14 @@ await init_spawn_subagent();
|
|
|
22737
23296
|
|
|
22738
23297
|
// ../../packages/core/src/agents/mcp-tools/visuals.ts
|
|
22739
23298
|
init_common();
|
|
22740
|
-
import { z as
|
|
23299
|
+
import { z as z6 } from "zod";
|
|
22741
23300
|
var VISUALS_MCP_KEY = "visuals";
|
|
22742
23301
|
var showHtmlTool = {
|
|
22743
23302
|
name: "show_html",
|
|
22744
23303
|
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
23304
|
schema: {
|
|
22746
|
-
html:
|
|
22747
|
-
title:
|
|
23305
|
+
html: z6.string().describe("Complete, self-contained HTML document or fragment to render."),
|
|
23306
|
+
title: z6.string().optional().describe("Optional title shown above the rendered card.")
|
|
22748
23307
|
},
|
|
22749
23308
|
async handler() {
|
|
22750
23309
|
return textResult("HTML card displayed to the user.");
|
|
@@ -22754,9 +23313,9 @@ var showMermaidTool = {
|
|
|
22754
23313
|
name: "show_mermaid",
|
|
22755
23314
|
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
23315
|
schema: {
|
|
22757
|
-
code:
|
|
22758
|
-
title:
|
|
22759
|
-
theme:
|
|
23316
|
+
code: z6.string().describe("Mermaid diagram source without markdown code fences."),
|
|
23317
|
+
title: z6.string().optional().describe("Optional title shown above the rendered card."),
|
|
23318
|
+
theme: z6.enum(["default", "neutral", "dark", "forest"]).optional().describe("Optional Mermaid theme. Defaults to neutral.")
|
|
22760
23319
|
},
|
|
22761
23320
|
async handler() {
|
|
22762
23321
|
return textResult("Mermaid diagram displayed to the user.");
|
|
@@ -22766,10 +23325,10 @@ var showImageTool = {
|
|
|
22766
23325
|
name: "show_image",
|
|
22767
23326
|
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
23327
|
schema: {
|
|
22769
|
-
file_path:
|
|
22770
|
-
file_id:
|
|
22771
|
-
title:
|
|
22772
|
-
alt:
|
|
23328
|
+
file_path: z6.string().optional().describe("Absolute or workspace-relative path to an image file."),
|
|
23329
|
+
file_id: z6.string().optional().describe("Full Otium uploaded file UUID for an image attached in this topic."),
|
|
23330
|
+
title: z6.string().optional().describe("Optional title shown above the rendered card."),
|
|
23331
|
+
alt: z6.string().optional().describe("Optional alt text for the image.")
|
|
22773
23332
|
},
|
|
22774
23333
|
async handler(input) {
|
|
22775
23334
|
if (!input?.file_path && !input?.file_id) {
|
|
@@ -22855,4 +23414,4 @@ export {
|
|
|
22855
23414
|
DEFAULT_SELF_CONFIG_PRODUCT
|
|
22856
23415
|
};
|
|
22857
23416
|
|
|
22858
|
-
//# debugId=
|
|
23417
|
+
//# debugId=A147953DF9A0AA1764756E2164756E21
|