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/mcp-factories.js
CHANGED
|
@@ -2457,6 +2457,9 @@ function initializeApiMessagesSchema() {
|
|
|
2457
2457
|
try {
|
|
2458
2458
|
db.exec("ALTER TABLE api_messages ADD COLUMN subagent_card TEXT");
|
|
2459
2459
|
} catch {}
|
|
2460
|
+
try {
|
|
2461
|
+
db.exec("ALTER TABLE api_messages ADD COLUMN vault_secret_request TEXT");
|
|
2462
|
+
} catch {}
|
|
2460
2463
|
db.exec("CREATE INDEX IF NOT EXISTS idx_api_messages_topic ON api_messages(topic_id)");
|
|
2461
2464
|
db.exec("CREATE INDEX IF NOT EXISTS idx_api_messages_thread_root ON api_messages(thread_root_id)");
|
|
2462
2465
|
}
|
|
@@ -2493,6 +2496,7 @@ function rowToDto(r) {
|
|
|
2493
2496
|
askUserQuestion: r.ask_user_question ? JSON.parse(r.ask_user_question) : undefined,
|
|
2494
2497
|
tellCard: r.tell_card ? JSON.parse(r.tell_card) : undefined,
|
|
2495
2498
|
subagentCard: r.subagent_card ? JSON.parse(r.subagent_card) : undefined,
|
|
2499
|
+
vaultSecretRequest: r.vault_secret_request ? JSON.parse(r.vault_secret_request) : undefined,
|
|
2496
2500
|
mentions: r.mentions ? JSON.parse(r.mentions) : undefined,
|
|
2497
2501
|
threadRootId: r.thread_root_id ?? undefined,
|
|
2498
2502
|
createdAt: r.created_at
|
|
@@ -2514,9 +2518,9 @@ function appendApiMessage(msg, options = {}) {
|
|
|
2514
2518
|
let inserted = false;
|
|
2515
2519
|
db.transaction(() => {
|
|
2516
2520
|
const result = db.query(`INSERT INTO api_messages
|
|
2517
|
-
(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)
|
|
2518
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
2519
|
-
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);
|
|
2521
|
+
(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)
|
|
2522
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
2523
|
+
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);
|
|
2520
2524
|
inserted = Number(result.changes ?? 0) > 0;
|
|
2521
2525
|
if (inserted && updateTopicLastMessageAt && !msg.deleted) {
|
|
2522
2526
|
db.query(`UPDATE api_topics
|
|
@@ -4573,6 +4577,16 @@ function vaultListDecryptableValues(userId) {
|
|
|
4573
4577
|
}
|
|
4574
4578
|
return entries;
|
|
4575
4579
|
}
|
|
4580
|
+
function vaultSet(userId, key, value2, description = "") {
|
|
4581
|
+
const normalizedKey = normalizeVaultKey(key);
|
|
4582
|
+
activeVaultDatabase().prepare(`INSERT INTO vault (user_id, key, value, description) VALUES (?, ?, ?, ?)
|
|
4583
|
+
ON CONFLICT (user_id, key) DO UPDATE
|
|
4584
|
+
SET value = excluded.value, description = excluded.description`).run(userId, normalizedKey, encryptVaultValue(userId, normalizedKey, value2, vaultMasterKey), description);
|
|
4585
|
+
}
|
|
4586
|
+
function vaultHasKey(userId, key) {
|
|
4587
|
+
const row = activeVaultDatabase().prepare("SELECT 1 FROM vault WHERE user_id = ? AND key = ?").get(userId, normalizeVaultKey(key));
|
|
4588
|
+
return row != null;
|
|
4589
|
+
}
|
|
4576
4590
|
function vaultGetValue(userId, key) {
|
|
4577
4591
|
const normalizedKey = normalizeVaultKey(key);
|
|
4578
4592
|
const row = activeVaultDatabase().prepare("SELECT key, value FROM vault WHERE user_id = ? AND key = ?").get(userId, normalizedKey);
|
|
@@ -4631,7 +4645,7 @@ function redactVaultSecrets(userId, text) {
|
|
|
4631
4645
|
}
|
|
4632
4646
|
return redacted;
|
|
4633
4647
|
}
|
|
4634
|
-
var vaultDb, vaultMasterKey, VAULT_VALUE_MAX_BYTES;
|
|
4648
|
+
var vaultDb, vaultMasterKey, VAULT_KEY_PATTERN, VAULT_VALUE_MIN_BYTES = 4, VAULT_VALUE_MAX_BYTES, VAULT_DESCRIPTION_MAX_LENGTH = 500;
|
|
4635
4649
|
var init_vault = __esm(async () => {
|
|
4636
4650
|
init_config();
|
|
4637
4651
|
init_vault_crypto();
|
|
@@ -4640,6 +4654,7 @@ var init_vault = __esm(async () => {
|
|
|
4640
4654
|
init_storage_host()
|
|
4641
4655
|
]);
|
|
4642
4656
|
vaultMasterKey = VAULT_MASTER_KEY;
|
|
4657
|
+
VAULT_KEY_PATTERN = /^[A-Z][A-Z0-9_]{0,127}$/;
|
|
4643
4658
|
VAULT_VALUE_MAX_BYTES = 64 * 1024;
|
|
4644
4659
|
});
|
|
4645
4660
|
|
|
@@ -5253,7 +5268,7 @@ var init_codex = __esm(async () => {
|
|
|
5253
5268
|
});
|
|
5254
5269
|
|
|
5255
5270
|
// ../../packages/core/src/version.ts
|
|
5256
|
-
var NEGOTIUM_VERSION = "0.22.
|
|
5271
|
+
var NEGOTIUM_VERSION = "0.22.4";
|
|
5257
5272
|
|
|
5258
5273
|
// ../../packages/core/src/agents/codex-native-multi-agent.ts
|
|
5259
5274
|
import { spawn as spawn2 } from "child_process";
|
|
@@ -18295,6 +18310,548 @@ var init_ask_user = __esm(async () => {
|
|
|
18295
18310
|
});
|
|
18296
18311
|
});
|
|
18297
18312
|
|
|
18313
|
+
// ../../packages/core/src/storage/vault-request-gates.ts
|
|
18314
|
+
function initializeVaultRequestGateSchema() {
|
|
18315
|
+
db.exec(`
|
|
18316
|
+
CREATE TABLE IF NOT EXISTS vault_request_gates (
|
|
18317
|
+
gate_id TEXT PRIMARY KEY,
|
|
18318
|
+
topic_id TEXT NOT NULL REFERENCES api_topics(id) ON DELETE CASCADE,
|
|
18319
|
+
query_id TEXT,
|
|
18320
|
+
idempotency_key TEXT NOT NULL,
|
|
18321
|
+
body_hash TEXT NOT NULL,
|
|
18322
|
+
message_id TEXT NOT NULL UNIQUE,
|
|
18323
|
+
owner_id TEXT NOT NULL,
|
|
18324
|
+
state TEXT NOT NULL,
|
|
18325
|
+
outcome TEXT,
|
|
18326
|
+
answered_by TEXT,
|
|
18327
|
+
claim_source TEXT,
|
|
18328
|
+
created_at TEXT NOT NULL,
|
|
18329
|
+
updated_at TEXT NOT NULL
|
|
18330
|
+
);
|
|
18331
|
+
CREATE INDEX IF NOT EXISTS idx_vault_request_gates_idempotency
|
|
18332
|
+
ON vault_request_gates(topic_id, idempotency_key, created_at);
|
|
18333
|
+
CREATE UNIQUE INDEX IF NOT EXISTS idx_vault_request_gates_active_key
|
|
18334
|
+
ON vault_request_gates(topic_id, idempotency_key)
|
|
18335
|
+
WHERE state IN ('pending', 'claimed');
|
|
18336
|
+
`);
|
|
18337
|
+
}
|
|
18338
|
+
function fromRow2(row) {
|
|
18339
|
+
return {
|
|
18340
|
+
gateId: row.gate_id,
|
|
18341
|
+
topicId: row.topic_id,
|
|
18342
|
+
queryId: row.query_id ?? undefined,
|
|
18343
|
+
idempotencyKey: row.idempotency_key,
|
|
18344
|
+
bodyHash: row.body_hash,
|
|
18345
|
+
messageId: row.message_id,
|
|
18346
|
+
ownerId: row.owner_id,
|
|
18347
|
+
state: row.state,
|
|
18348
|
+
outcome: row.outcome ?? undefined,
|
|
18349
|
+
answeredBy: row.answered_by ?? undefined
|
|
18350
|
+
};
|
|
18351
|
+
}
|
|
18352
|
+
function latestGate2(topicId, idempotencyKey) {
|
|
18353
|
+
const row = db.query(`SELECT * FROM vault_request_gates
|
|
18354
|
+
WHERE topic_id = ? AND idempotency_key = ?
|
|
18355
|
+
ORDER BY rowid DESC LIMIT 1`).get(topicId, idempotencyKey);
|
|
18356
|
+
return row ? fromRow2(row) : null;
|
|
18357
|
+
}
|
|
18358
|
+
function expireVaultCard(topicId, messageId, editedAt) {
|
|
18359
|
+
const row = db.query(`SELECT vault_secret_request FROM api_messages
|
|
18360
|
+
WHERE topic_id = ? AND id = ? AND deleted = 0 AND kind = 'vault_secret_request'`).get(topicId, messageId);
|
|
18361
|
+
if (!row?.vault_secret_request)
|
|
18362
|
+
return null;
|
|
18363
|
+
const request = JSON.parse(row.vault_secret_request);
|
|
18364
|
+
if (request.status !== "pending")
|
|
18365
|
+
return null;
|
|
18366
|
+
const expired = { ...request, status: "expired" };
|
|
18367
|
+
db.query(`UPDATE api_messages SET vault_secret_request = ?, edited_at = ?
|
|
18368
|
+
WHERE topic_id = ? AND id = ? AND deleted = 0 AND kind = 'vault_secret_request'`).run(JSON.stringify(expired), editedAt, topicId, messageId);
|
|
18369
|
+
return { topicId, messageId, vaultSecretRequest: expired, editedAt };
|
|
18370
|
+
}
|
|
18371
|
+
function prepareVaultRequestGate(args) {
|
|
18372
|
+
return db.transaction(() => {
|
|
18373
|
+
const latest = latestGate2(args.topicId, args.idempotencyKey);
|
|
18374
|
+
if (latest?.bodyHash !== undefined && latest.bodyHash !== args.bodyHash) {
|
|
18375
|
+
return { outcome: "conflict", gate: latest };
|
|
18376
|
+
}
|
|
18377
|
+
if (latest?.state === "answered")
|
|
18378
|
+
return { outcome: "replay", gate: latest };
|
|
18379
|
+
if (latest && (latest.state === "pending" || latest.state === "claimed")) {
|
|
18380
|
+
return { outcome: "pending", gate: latest };
|
|
18381
|
+
}
|
|
18382
|
+
db.query(`INSERT INTO vault_request_gates
|
|
18383
|
+
(gate_id, topic_id, query_id, idempotency_key, body_hash, message_id, owner_id, state, created_at, updated_at)
|
|
18384
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, 'pending', ?, ?)`).run(args.gateId, args.topicId, args.queryId ?? null, args.idempotencyKey, args.bodyHash, args.messageId, args.ownerId, args.now, args.now);
|
|
18385
|
+
return {
|
|
18386
|
+
outcome: "created",
|
|
18387
|
+
gate: {
|
|
18388
|
+
gateId: args.gateId,
|
|
18389
|
+
topicId: args.topicId,
|
|
18390
|
+
queryId: args.queryId,
|
|
18391
|
+
idempotencyKey: args.idempotencyKey,
|
|
18392
|
+
bodyHash: args.bodyHash,
|
|
18393
|
+
messageId: args.messageId,
|
|
18394
|
+
ownerId: args.ownerId,
|
|
18395
|
+
state: "pending"
|
|
18396
|
+
}
|
|
18397
|
+
};
|
|
18398
|
+
}).immediate();
|
|
18399
|
+
}
|
|
18400
|
+
function quarantineVaultRequestGate(gateId, ownerId, now = new Date().toISOString()) {
|
|
18401
|
+
return db.transaction(() => {
|
|
18402
|
+
const row = db.query("SELECT * FROM vault_request_gates WHERE gate_id = ?").get(gateId);
|
|
18403
|
+
if (!row || row.owner_id !== ownerId || !["pending", "claimed"].includes(row.state)) {
|
|
18404
|
+
return null;
|
|
18405
|
+
}
|
|
18406
|
+
const result = db.query(`UPDATE vault_request_gates SET state = 'quarantined', updated_at = ?
|
|
18407
|
+
WHERE gate_id = ? AND owner_id = ? AND state IN ('pending', 'claimed')`).run(now, gateId, ownerId);
|
|
18408
|
+
if (Number(result.changes ?? 0) === 0)
|
|
18409
|
+
return null;
|
|
18410
|
+
return expireVaultCard(row.topic_id, row.message_id, now);
|
|
18411
|
+
}).immediate();
|
|
18412
|
+
}
|
|
18413
|
+
function quarantineForeignVaultRequestGates(liveOwnerIds, now = new Date().toISOString()) {
|
|
18414
|
+
return db.transaction(() => {
|
|
18415
|
+
const rows = db.query(`SELECT * FROM vault_request_gates
|
|
18416
|
+
WHERE state IN ('pending', 'claimed')`).all();
|
|
18417
|
+
const updates = [];
|
|
18418
|
+
for (const row of rows) {
|
|
18419
|
+
if (liveOwnerIds.has(row.owner_id))
|
|
18420
|
+
continue;
|
|
18421
|
+
if (row.state === "claimed" && row.outcome && row.answered_by) {
|
|
18422
|
+
db.query(`UPDATE vault_request_gates SET state = 'answered', updated_at = ?
|
|
18423
|
+
WHERE gate_id = ? AND state = 'claimed'`).run(now, row.gate_id);
|
|
18424
|
+
continue;
|
|
18425
|
+
}
|
|
18426
|
+
const result = db.query(`UPDATE vault_request_gates SET state = 'quarantined', updated_at = ?
|
|
18427
|
+
WHERE gate_id = ? AND state IN ('pending', 'claimed')`).run(now, row.gate_id);
|
|
18428
|
+
if (Number(result.changes ?? 0) === 0)
|
|
18429
|
+
continue;
|
|
18430
|
+
const update = expireVaultCard(row.topic_id, row.message_id, now);
|
|
18431
|
+
if (update)
|
|
18432
|
+
updates.push(update);
|
|
18433
|
+
}
|
|
18434
|
+
const legacyCards = db.query(`SELECT m.topic_id, m.id
|
|
18435
|
+
FROM api_messages m
|
|
18436
|
+
LEFT JOIN vault_request_gates g ON g.message_id = m.id
|
|
18437
|
+
WHERE m.kind = 'vault_secret_request'
|
|
18438
|
+
AND m.deleted = 0
|
|
18439
|
+
AND g.gate_id IS NULL`).all();
|
|
18440
|
+
for (const card of legacyCards) {
|
|
18441
|
+
const update = expireVaultCard(card.topic_id, card.id, now);
|
|
18442
|
+
if (update)
|
|
18443
|
+
updates.push(update);
|
|
18444
|
+
}
|
|
18445
|
+
return updates;
|
|
18446
|
+
}).immediate();
|
|
18447
|
+
}
|
|
18448
|
+
function claimVaultRequestGateAndAnswer(args) {
|
|
18449
|
+
return db.transaction(() => {
|
|
18450
|
+
const row = db.query(`SELECT * FROM vault_request_gates
|
|
18451
|
+
WHERE topic_id = ? AND message_id = ?
|
|
18452
|
+
ORDER BY rowid DESC LIMIT 1`).get(args.topicId, args.messageId);
|
|
18453
|
+
if (!row || row.owner_id !== args.ownerId || row.state !== "pending") {
|
|
18454
|
+
return { outcome: "unavailable" };
|
|
18455
|
+
}
|
|
18456
|
+
const message = db.query(`SELECT vault_secret_request FROM api_messages
|
|
18457
|
+
WHERE topic_id = ? AND id = ? AND deleted = 0 AND kind = 'vault_secret_request'`).get(args.topicId, args.messageId);
|
|
18458
|
+
if (!message?.vault_secret_request)
|
|
18459
|
+
return { outcome: "unavailable" };
|
|
18460
|
+
const request = JSON.parse(message.vault_secret_request);
|
|
18461
|
+
if (request.status !== "pending")
|
|
18462
|
+
return { outcome: "unavailable" };
|
|
18463
|
+
const claimed = db.query(`UPDATE vault_request_gates
|
|
18464
|
+
SET state = 'answered', outcome = ?, answered_by = ?, claim_source = ?, updated_at = ?
|
|
18465
|
+
WHERE gate_id = ? AND owner_id = ? AND state = 'pending'`).run(args.outcome, args.userId, args.source, args.now, row.gate_id, args.ownerId);
|
|
18466
|
+
if (Number(claimed.changes ?? 0) === 0)
|
|
18467
|
+
return { outcome: "unavailable" };
|
|
18468
|
+
const answered = {
|
|
18469
|
+
...request,
|
|
18470
|
+
status: args.outcome,
|
|
18471
|
+
answeredBy: args.userId
|
|
18472
|
+
};
|
|
18473
|
+
db.query(`UPDATE api_messages SET vault_secret_request = ?, edited_at = ?
|
|
18474
|
+
WHERE topic_id = ? AND id = ? AND deleted = 0 AND kind = 'vault_secret_request'`).run(JSON.stringify(answered), args.now, args.topicId, args.messageId);
|
|
18475
|
+
return {
|
|
18476
|
+
outcome: "claimed",
|
|
18477
|
+
gate: fromRow2({
|
|
18478
|
+
...row,
|
|
18479
|
+
state: "answered",
|
|
18480
|
+
outcome: args.outcome,
|
|
18481
|
+
answered_by: args.userId
|
|
18482
|
+
}),
|
|
18483
|
+
vaultSecretRequest: answered,
|
|
18484
|
+
editedAt: args.now
|
|
18485
|
+
};
|
|
18486
|
+
}).immediate();
|
|
18487
|
+
}
|
|
18488
|
+
function cancelVaultRequestGate(topicId, messageId, ownerId, now = new Date().toISOString()) {
|
|
18489
|
+
return db.transaction(() => {
|
|
18490
|
+
const row = db.query(`SELECT * FROM vault_request_gates
|
|
18491
|
+
WHERE topic_id = ? AND message_id = ? ORDER BY rowid DESC LIMIT 1`).get(topicId, messageId);
|
|
18492
|
+
if (!row || row.owner_id !== ownerId || row.state !== "pending")
|
|
18493
|
+
return null;
|
|
18494
|
+
const result = db.query(`UPDATE vault_request_gates SET state = 'cancelled', updated_at = ?
|
|
18495
|
+
WHERE gate_id = ? AND owner_id = ? AND state = 'pending'`).run(now, row.gate_id, ownerId);
|
|
18496
|
+
if (Number(result.changes ?? 0) === 0)
|
|
18497
|
+
return null;
|
|
18498
|
+
return expireVaultCard(topicId, messageId, now);
|
|
18499
|
+
}).immediate();
|
|
18500
|
+
}
|
|
18501
|
+
var init_vault_request_gates = __esm(async () => {
|
|
18502
|
+
await __promiseAll([
|
|
18503
|
+
init_forum_db(),
|
|
18504
|
+
init_storage_host()
|
|
18505
|
+
]);
|
|
18506
|
+
registerStorageSchemaInitializer(initializeVaultRequestGateSchema, 32);
|
|
18507
|
+
});
|
|
18508
|
+
|
|
18509
|
+
// ../../packages/core/src/agents/mcp-tools/request-vault.ts
|
|
18510
|
+
import { createHash as createHash12, randomUUID as randomUUID17 } from "crypto";
|
|
18511
|
+
import { z as z5 } from "zod";
|
|
18512
|
+
function requestBodyHash(key, description) {
|
|
18513
|
+
return createHash12("sha256").update(JSON.stringify({ key, description })).digest("hex");
|
|
18514
|
+
}
|
|
18515
|
+
function answerToolResult2(key, outcome) {
|
|
18516
|
+
if (outcome === "no_answer") {
|
|
18517
|
+
return errorResult("The AI turn ended before the user answered.");
|
|
18518
|
+
}
|
|
18519
|
+
if (outcome === "declined") {
|
|
18520
|
+
return errorResult(`User declined to provide a value for ${key}.`);
|
|
18521
|
+
}
|
|
18522
|
+
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.`);
|
|
18523
|
+
}
|
|
18524
|
+
function createRequestVaultRuntime(host) {
|
|
18525
|
+
const pendingRequests = new Map;
|
|
18526
|
+
const { ownerId } = host.runtime;
|
|
18527
|
+
const createId = () => host.runtime.createId();
|
|
18528
|
+
const now = () => host.runtime.now();
|
|
18529
|
+
const isProcessLeaseAlive = (lease) => host.durability.processLeases.isAlive(lease);
|
|
18530
|
+
let gateOwnerLease = null;
|
|
18531
|
+
function broadcastGateUpdates(updates) {
|
|
18532
|
+
for (const update of updates)
|
|
18533
|
+
host.messaging.publication.publishCardUpdate(update);
|
|
18534
|
+
}
|
|
18535
|
+
function startGateOwner() {
|
|
18536
|
+
if (gateOwnerLease)
|
|
18537
|
+
return;
|
|
18538
|
+
gateOwnerLease = host.durability.processLeases.acquire(`${VAULT_GATE_LEASE_ROLE_PREFIX}${ownerId}`, ownerId);
|
|
18539
|
+
if (!gateOwnerLease)
|
|
18540
|
+
throw new Error("failed to acquire the vault-request gate owner lease");
|
|
18541
|
+
}
|
|
18542
|
+
function stopGateOwner() {
|
|
18543
|
+
gateOwnerLease?.stop();
|
|
18544
|
+
gateOwnerLease = null;
|
|
18545
|
+
}
|
|
18546
|
+
function reconcilePendingGates() {
|
|
18547
|
+
startGateOwner();
|
|
18548
|
+
host.durability.processLeases.removeDead(VAULT_GATE_LEASE_ROLE_PREFIX);
|
|
18549
|
+
const liveOwners = new Set(host.durability.processLeases.list(VAULT_GATE_LEASE_ROLE_PREFIX).filter(isProcessLeaseAlive).map((lease) => lease.ownerId));
|
|
18550
|
+
const updates = host.durability.gates.quarantineForeign(liveOwners, now());
|
|
18551
|
+
broadcastGateUpdates(updates);
|
|
18552
|
+
return updates.length;
|
|
18553
|
+
}
|
|
18554
|
+
function answerPendingRequest(topicId, messageId, userId, value2) {
|
|
18555
|
+
reconcilePendingGates();
|
|
18556
|
+
const topic = host.topics.getTopic(topicId);
|
|
18557
|
+
if (!topic?.participants.some((participant) => participant.userId === userId)) {
|
|
18558
|
+
return { ok: false, error: "User is not a member of this topic." };
|
|
18559
|
+
}
|
|
18560
|
+
const existing = host.messaging.persistence.getMessage(topicId, messageId);
|
|
18561
|
+
const request = existing?.kind === "vault_secret_request" ? existing.vaultSecretRequest : undefined;
|
|
18562
|
+
if (!existing || existing.deleted || !request) {
|
|
18563
|
+
return { ok: false, error: "Vault request card not found." };
|
|
18564
|
+
}
|
|
18565
|
+
if (request.status !== "pending") {
|
|
18566
|
+
return { ok: false, error: "This request is no longer awaiting an answer." };
|
|
18567
|
+
}
|
|
18568
|
+
const pending = pendingRequests.get(messageId);
|
|
18569
|
+
if (!pending || pending.topicId !== topicId) {
|
|
18570
|
+
return { ok: false, error: "This request is no longer awaiting an answer." };
|
|
18571
|
+
}
|
|
18572
|
+
const declining = value2 === null;
|
|
18573
|
+
if (!declining) {
|
|
18574
|
+
const bytes = Buffer.byteLength(value2, "utf8");
|
|
18575
|
+
if (bytes < VAULT_VALUE_MIN_BYTES || bytes > VAULT_VALUE_MAX_BYTES) {
|
|
18576
|
+
return {
|
|
18577
|
+
ok: false,
|
|
18578
|
+
error: `Value must be between ${VAULT_VALUE_MIN_BYTES} and ${VAULT_VALUE_MAX_BYTES} bytes.`
|
|
18579
|
+
};
|
|
18580
|
+
}
|
|
18581
|
+
}
|
|
18582
|
+
if (!declining) {
|
|
18583
|
+
try {
|
|
18584
|
+
host.vault.set(userId, pending.key, value2, pending.description);
|
|
18585
|
+
} catch (error) {
|
|
18586
|
+
return {
|
|
18587
|
+
ok: false,
|
|
18588
|
+
error: `Failed to store the value: ${error instanceof Error ? error.message : String(error)}`
|
|
18589
|
+
};
|
|
18590
|
+
}
|
|
18591
|
+
}
|
|
18592
|
+
const outcome = declining ? "declined" : "fulfilled";
|
|
18593
|
+
let claim;
|
|
18594
|
+
try {
|
|
18595
|
+
claim = host.durability.gates.claimAndAnswer({
|
|
18596
|
+
topicId,
|
|
18597
|
+
messageId,
|
|
18598
|
+
outcome,
|
|
18599
|
+
userId,
|
|
18600
|
+
ownerId,
|
|
18601
|
+
source: "host-control",
|
|
18602
|
+
now: now()
|
|
18603
|
+
});
|
|
18604
|
+
} catch {
|
|
18605
|
+
return { ok: false, error: "This request could not be claimed safely. Retry once." };
|
|
18606
|
+
}
|
|
18607
|
+
if (claim.outcome !== "claimed") {
|
|
18608
|
+
return { ok: false, error: "This request is no longer awaiting an answer." };
|
|
18609
|
+
}
|
|
18610
|
+
pendingRequests.delete(messageId);
|
|
18611
|
+
const answerMessage = {
|
|
18612
|
+
id: createId(),
|
|
18613
|
+
topicId,
|
|
18614
|
+
authorId: userId,
|
|
18615
|
+
text: outcome === "fulfilled" ? `Provided a value for ${pending.key}.` : `Declined to provide a value for ${pending.key}.`,
|
|
18616
|
+
parentId: messageId,
|
|
18617
|
+
...existing.threadRootId ? { threadRootId: existing.threadRootId } : {},
|
|
18618
|
+
createdAt: now()
|
|
18619
|
+
};
|
|
18620
|
+
try {
|
|
18621
|
+
host.messaging.publication.publishCardUpdate({
|
|
18622
|
+
topicId,
|
|
18623
|
+
messageId,
|
|
18624
|
+
vaultSecretRequest: claim.vaultSecretRequest,
|
|
18625
|
+
editedAt: claim.editedAt
|
|
18626
|
+
});
|
|
18627
|
+
host.messaging.persistence.appendMessage(answerMessage);
|
|
18628
|
+
host.messaging.publication.publishMessage(topicId, answerMessage);
|
|
18629
|
+
} catch {}
|
|
18630
|
+
pending.resolve({ outcome, userId });
|
|
18631
|
+
return { ok: true, queryId: pending.queryId, outcome, answerMessage };
|
|
18632
|
+
}
|
|
18633
|
+
function cancelPendingRequests(topicId, queryId) {
|
|
18634
|
+
for (const [messageId, pending] of pendingRequests) {
|
|
18635
|
+
if (pending.topicId === topicId && pending.queryId === queryId) {
|
|
18636
|
+
pendingRequests.delete(messageId);
|
|
18637
|
+
const update = host.durability.gates.cancel(topicId, messageId, ownerId, now());
|
|
18638
|
+
if (update)
|
|
18639
|
+
broadcastGateUpdates([update]);
|
|
18640
|
+
pending.resolve({ outcome: "no_answer", userId: "" });
|
|
18641
|
+
}
|
|
18642
|
+
}
|
|
18643
|
+
}
|
|
18644
|
+
function appendRequestMessage(ctx, key, description, messageId) {
|
|
18645
|
+
const cfg = host.topics.getConfig(ctx.topicId);
|
|
18646
|
+
const vaultSecretRequest = { key, description, status: "pending" };
|
|
18647
|
+
const message = {
|
|
18648
|
+
id: messageId,
|
|
18649
|
+
topicId: ctx.topicId,
|
|
18650
|
+
authorId: "ai",
|
|
18651
|
+
text: `Vault secret requested: ${key}`,
|
|
18652
|
+
queryId: ctx.queryId,
|
|
18653
|
+
agentType: ctx.agent,
|
|
18654
|
+
model: ctx.model ?? cfg?.model ?? "unknown",
|
|
18655
|
+
kind: "vault_secret_request",
|
|
18656
|
+
vaultSecretRequest,
|
|
18657
|
+
...ctx.threadRootId ? { threadRootId: ctx.threadRootId } : {},
|
|
18658
|
+
createdAt: now()
|
|
18659
|
+
};
|
|
18660
|
+
host.messaging.persistence.appendMessage(message);
|
|
18661
|
+
return message;
|
|
18662
|
+
}
|
|
18663
|
+
async function requestVaultSecret(ctx, input) {
|
|
18664
|
+
if (ctx.unsupportedSurface) {
|
|
18665
|
+
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.");
|
|
18666
|
+
}
|
|
18667
|
+
reconcilePendingGates();
|
|
18668
|
+
const topic = host.topics.getTopic(ctx.topicId);
|
|
18669
|
+
if (!topic)
|
|
18670
|
+
return errorResult(`Error: topic '${ctx.topicId}' not found.`);
|
|
18671
|
+
if (!topic.participants.some((participant) => participant.userId === ctx.userId)) {
|
|
18672
|
+
return errorResult("Error: user is not a member of this topic.");
|
|
18673
|
+
}
|
|
18674
|
+
if (typeof input.key !== "string" || !input.key.trim()) {
|
|
18675
|
+
return errorResult("Error: key is required.");
|
|
18676
|
+
}
|
|
18677
|
+
const key = normalizeVaultKey(input.key);
|
|
18678
|
+
if (!VAULT_KEY_PATTERN.test(key)) {
|
|
18679
|
+
return errorResult("Error: key must start with a letter and contain only uppercase letters, digits, and underscores (max 128 chars).");
|
|
18680
|
+
}
|
|
18681
|
+
if (typeof input.description !== "string" || !input.description.trim()) {
|
|
18682
|
+
return errorResult("Error: description is required so the user knows what to type.");
|
|
18683
|
+
}
|
|
18684
|
+
const description = input.description.trim().slice(0, MAX_DESCRIPTION_CHARS);
|
|
18685
|
+
if (input.idempotency_key !== undefined && (typeof input.idempotency_key !== "string" || !input.idempotency_key.trim())) {
|
|
18686
|
+
return errorResult("Error: idempotency_key must be a non-empty string.");
|
|
18687
|
+
}
|
|
18688
|
+
const rawIdempotencyKey = typeof input.idempotency_key === "string" ? input.idempotency_key.trim() : createId();
|
|
18689
|
+
if (rawIdempotencyKey.length > MAX_IDEMPOTENCY_KEY_CHARS2) {
|
|
18690
|
+
return errorResult(`Error: idempotency_key must be at most ${MAX_IDEMPOTENCY_KEY_CHARS2} characters.`);
|
|
18691
|
+
}
|
|
18692
|
+
const bodyHash = requestBodyHash(key, description);
|
|
18693
|
+
const createGate = () => {
|
|
18694
|
+
const suffix = createId();
|
|
18695
|
+
return host.durability.gates.prepare({
|
|
18696
|
+
gateId: `vault-request-gate-${suffix}`,
|
|
18697
|
+
topicId: ctx.topicId,
|
|
18698
|
+
queryId: ctx.queryId,
|
|
18699
|
+
idempotencyKey: rawIdempotencyKey,
|
|
18700
|
+
bodyHash,
|
|
18701
|
+
messageId: `vault-request-${ctx.queryId ?? "runtime"}-${suffix}`,
|
|
18702
|
+
ownerId,
|
|
18703
|
+
now: now()
|
|
18704
|
+
});
|
|
18705
|
+
};
|
|
18706
|
+
let prepared = createGate();
|
|
18707
|
+
if (prepared.outcome === "conflict") {
|
|
18708
|
+
return errorResult("Error: idempotency_conflict. The same idempotency_key was already used with a different key or description.");
|
|
18709
|
+
}
|
|
18710
|
+
if (prepared.outcome === "replay") {
|
|
18711
|
+
if (!prepared.gate.outcome || !prepared.gate.answeredBy) {
|
|
18712
|
+
return errorResult("Error: the stored request_vault_secret replay is incomplete.");
|
|
18713
|
+
}
|
|
18714
|
+
return answerToolResult2(key, prepared.gate.outcome);
|
|
18715
|
+
}
|
|
18716
|
+
if (prepared.outcome === "pending") {
|
|
18717
|
+
if (prepared.gate.ownerId !== ownerId) {
|
|
18718
|
+
return errorResult("Error: this request_vault_secret is pending in another healthy runtime process.");
|
|
18719
|
+
}
|
|
18720
|
+
const existingPending = pendingRequests.get(prepared.gate.messageId);
|
|
18721
|
+
if (existingPending)
|
|
18722
|
+
return answerToolResult2(key, (await existingPending.promise).outcome);
|
|
18723
|
+
if (prepared.gate.state === "claimed") {
|
|
18724
|
+
return errorResult("Error: this request_vault_secret is already being finalized.");
|
|
18725
|
+
}
|
|
18726
|
+
const update = host.durability.gates.quarantine(prepared.gate.gateId, ownerId, now());
|
|
18727
|
+
if (update)
|
|
18728
|
+
broadcastGateUpdates([update]);
|
|
18729
|
+
prepared = createGate();
|
|
18730
|
+
}
|
|
18731
|
+
if (prepared.outcome !== "created") {
|
|
18732
|
+
return errorResult("Error: failed to create a durable request_vault_secret gate.");
|
|
18733
|
+
}
|
|
18734
|
+
if (host.vault.hasKey(ctx.userId, key)) {
|
|
18735
|
+
host.durability.gates.quarantine(prepared.gate.gateId, ownerId, now());
|
|
18736
|
+
return errorResult(`Error: vault key '${key}' already exists. Pick a different key, or ask the user to update it from their Vault manager.`);
|
|
18737
|
+
}
|
|
18738
|
+
let message;
|
|
18739
|
+
try {
|
|
18740
|
+
message = appendRequestMessage(ctx, key, description, prepared.gate.messageId);
|
|
18741
|
+
} catch (error) {
|
|
18742
|
+
host.durability.gates.quarantine(prepared.gate.gateId, ownerId, now());
|
|
18743
|
+
return errorResult(`Error: failed to persist request_vault_secret: ${error instanceof Error ? error.message : String(error)}`);
|
|
18744
|
+
}
|
|
18745
|
+
let resolveAnswer;
|
|
18746
|
+
const promise = new Promise((resolve17) => {
|
|
18747
|
+
resolveAnswer = resolve17;
|
|
18748
|
+
});
|
|
18749
|
+
pendingRequests.set(message.id, {
|
|
18750
|
+
topicId: ctx.topicId,
|
|
18751
|
+
queryId: ctx.queryId,
|
|
18752
|
+
messageId: message.id,
|
|
18753
|
+
key,
|
|
18754
|
+
description,
|
|
18755
|
+
promise,
|
|
18756
|
+
resolve: resolveAnswer
|
|
18757
|
+
});
|
|
18758
|
+
try {
|
|
18759
|
+
host.messaging.publication.publishMessage(ctx.topicId, message);
|
|
18760
|
+
} catch (error) {
|
|
18761
|
+
pendingRequests.delete(message.id);
|
|
18762
|
+
try {
|
|
18763
|
+
host.durability.gates.quarantine(prepared.gate.gateId, ownerId, now());
|
|
18764
|
+
} catch {}
|
|
18765
|
+
return errorResult(`Error: failed to publish request_vault_secret: ${error instanceof Error ? error.message : String(error)}`);
|
|
18766
|
+
}
|
|
18767
|
+
return answerToolResult2(key, (await promise).outcome);
|
|
18768
|
+
}
|
|
18769
|
+
function createToolDefinition(ctx) {
|
|
18770
|
+
return {
|
|
18771
|
+
name: "request_vault_secret",
|
|
18772
|
+
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.",
|
|
18773
|
+
schema: {
|
|
18774
|
+
key: z5.string().describe("New Vault key name, e.g. STRIPE_API_KEY. Letters/digits/underscores, must start with a letter."),
|
|
18775
|
+
description: z5.string().max(MAX_DESCRIPTION_CHARS).describe("One sentence telling the user what value to paste and where it's from."),
|
|
18776
|
+
idempotency_key: z5.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.")
|
|
18777
|
+
},
|
|
18778
|
+
async handler(input) {
|
|
18779
|
+
return requestVaultSecret(ctx, input);
|
|
18780
|
+
}
|
|
18781
|
+
};
|
|
18782
|
+
}
|
|
18783
|
+
return {
|
|
18784
|
+
startGateOwner,
|
|
18785
|
+
stopGateOwner,
|
|
18786
|
+
reconcilePendingGates,
|
|
18787
|
+
answerPendingRequest,
|
|
18788
|
+
cancelPendingRequests,
|
|
18789
|
+
createToolDefinition
|
|
18790
|
+
};
|
|
18791
|
+
}
|
|
18792
|
+
function cancelPendingVaultSecretRequests(topicId, queryId) {
|
|
18793
|
+
defaultRequestVaultRuntime.cancelPendingRequests(topicId, queryId);
|
|
18794
|
+
}
|
|
18795
|
+
var MAX_DESCRIPTION_CHARS, MAX_IDEMPOTENCY_KEY_CHARS2 = 200, VAULT_GATE_OWNER_ID, VAULT_GATE_LEASE_ROLE_PREFIX = "vault-request-gate:", defaultRequestVaultDurabilityHost, defaultRequestVaultRuntime;
|
|
18796
|
+
var init_request_vault = __esm(async () => {
|
|
18797
|
+
init_common();
|
|
18798
|
+
await __promiseAll([
|
|
18799
|
+
init_bus(),
|
|
18800
|
+
init_api_messages(),
|
|
18801
|
+
init_api_topic_config(),
|
|
18802
|
+
init_api_topics(),
|
|
18803
|
+
init_runtime_process_leases(),
|
|
18804
|
+
init_vault(),
|
|
18805
|
+
init_vault_request_gates()
|
|
18806
|
+
]);
|
|
18807
|
+
MAX_DESCRIPTION_CHARS = VAULT_DESCRIPTION_MAX_LENGTH;
|
|
18808
|
+
VAULT_GATE_OWNER_ID = `request-vault-${process.pid}-${randomUUID17()}`;
|
|
18809
|
+
defaultRequestVaultDurabilityHost = {
|
|
18810
|
+
gates: {
|
|
18811
|
+
prepare: prepareVaultRequestGate,
|
|
18812
|
+
claimAndAnswer: claimVaultRequestGateAndAnswer,
|
|
18813
|
+
cancel: cancelVaultRequestGate,
|
|
18814
|
+
quarantine: quarantineVaultRequestGate,
|
|
18815
|
+
quarantineForeign: quarantineForeignVaultRequestGates
|
|
18816
|
+
},
|
|
18817
|
+
processLeases: {
|
|
18818
|
+
acquire: (role, ownerId) => acquireRuntimeProcessLease(role, { ownerId }),
|
|
18819
|
+
list: listRuntimeProcessLeases,
|
|
18820
|
+
isAlive: isRuntimeProcessLeaseAlive,
|
|
18821
|
+
removeDead: removeDeadRuntimeProcessLeases
|
|
18822
|
+
}
|
|
18823
|
+
};
|
|
18824
|
+
defaultRequestVaultRuntime = createRequestVaultRuntime({
|
|
18825
|
+
messaging: {
|
|
18826
|
+
persistence: {
|
|
18827
|
+
getMessage: getApiMessage,
|
|
18828
|
+
appendMessage: appendApiMessage
|
|
18829
|
+
},
|
|
18830
|
+
publication: {
|
|
18831
|
+
publishMessage: (topicId, message) => WsHub.get().broadcastMessage(topicId, message),
|
|
18832
|
+
publishCardUpdate: (update) => WsHub.get().broadcastMessageUpdated(update.topicId, update.messageId, {
|
|
18833
|
+
vaultSecretRequest: update.vaultSecretRequest,
|
|
18834
|
+
editedAt: update.editedAt
|
|
18835
|
+
})
|
|
18836
|
+
}
|
|
18837
|
+
},
|
|
18838
|
+
topics: {
|
|
18839
|
+
getTopic,
|
|
18840
|
+
getConfig: getApiTopicConfig
|
|
18841
|
+
},
|
|
18842
|
+
vault: {
|
|
18843
|
+
hasKey: vaultHasKey,
|
|
18844
|
+
set: vaultSet
|
|
18845
|
+
},
|
|
18846
|
+
durability: defaultRequestVaultDurabilityHost,
|
|
18847
|
+
runtime: {
|
|
18848
|
+
ownerId: VAULT_GATE_OWNER_ID,
|
|
18849
|
+
createId: randomUUID17,
|
|
18850
|
+
now: () => new Date().toISOString()
|
|
18851
|
+
}
|
|
18852
|
+
});
|
|
18853
|
+
});
|
|
18854
|
+
|
|
18298
18855
|
// ../../packages/core/src/mcp/peer-bridge.ts
|
|
18299
18856
|
function flushPeerRuntimeEvents(localTopicId) {
|
|
18300
18857
|
return activeBridge2?.flushEvents?.(localTopicId) ?? Promise.resolve(true);
|
|
@@ -18669,7 +19226,7 @@ var init_visuals = __esm(async () => {
|
|
|
18669
19226
|
});
|
|
18670
19227
|
|
|
18671
19228
|
// ../../packages/core/src/runtime/turn-event-stream.ts
|
|
18672
|
-
import { randomUUID as
|
|
19229
|
+
import { randomUUID as randomUUID18 } from "crypto";
|
|
18673
19230
|
import { realpathSync as realpathSync7, statSync as statSync9 } from "fs";
|
|
18674
19231
|
import { isAbsolute as isAbsolute6, resolve as resolve18 } from "path";
|
|
18675
19232
|
function sessionEventMatchesCurrentExecution(topicId, queryId, agent, model) {
|
|
@@ -18776,7 +19333,7 @@ async function runTurnEventStream(topicId, topicTitle, queryId, events, control,
|
|
|
18776
19333
|
if (silent || !text2.trim())
|
|
18777
19334
|
return null;
|
|
18778
19335
|
const message = {
|
|
18779
|
-
id:
|
|
19336
|
+
id: randomUUID18(),
|
|
18780
19337
|
topicId,
|
|
18781
19338
|
authorId: "ai",
|
|
18782
19339
|
text: text2,
|
|
@@ -19256,6 +19813,7 @@ ${JSON.stringify(event.input ?? {})}`);
|
|
|
19256
19813
|
const stillCurrent = getRoomQuery(roomId)?.queryId === queryId;
|
|
19257
19814
|
clearRoomQuery(roomId, queryId);
|
|
19258
19815
|
cancelPendingAskUserQuestions(topicId, queryId);
|
|
19816
|
+
cancelPendingVaultSecretRequests(topicId, queryId);
|
|
19259
19817
|
if (stillCurrent && roomId === topicId)
|
|
19260
19818
|
clearQueryState(userId, topicId, topicTitle);
|
|
19261
19819
|
if (!silent && stillCurrent)
|
|
@@ -19299,6 +19857,7 @@ var init_turn_event_stream = __esm(async () => {
|
|
|
19299
19857
|
init_idle_archiver(),
|
|
19300
19858
|
init_idle_compact(),
|
|
19301
19859
|
init_ask_user(),
|
|
19860
|
+
init_request_vault(),
|
|
19302
19861
|
init_spawn_subagent(),
|
|
19303
19862
|
init_registry(),
|
|
19304
19863
|
init_bus(),
|
|
@@ -19453,7 +20012,7 @@ __export(exports_turn_runner, {
|
|
|
19453
20012
|
FALLBACK_ORDER: () => FALLBACK_ORDER,
|
|
19454
20013
|
AGENT_DISPLAY_NAME: () => AGENT_DISPLAY_NAME
|
|
19455
20014
|
});
|
|
19456
|
-
import { randomUUID as
|
|
20015
|
+
import { randomUUID as randomUUID19 } from "crypto";
|
|
19457
20016
|
import { existsSync as existsSync24, mkdirSync as mkdirSync21, readdirSync as readdirSync7, statSync as statSync10 } from "fs";
|
|
19458
20017
|
import { join as join33 } from "path";
|
|
19459
20018
|
function withDefaultPlaywright(configuredMcp, isManager) {
|
|
@@ -19478,7 +20037,7 @@ ${reminderPrompt}` };
|
|
|
19478
20037
|
}
|
|
19479
20038
|
function appendSystemMessage(topicId, text2, threadRootId) {
|
|
19480
20039
|
const message = {
|
|
19481
|
-
id:
|
|
20040
|
+
id: randomUUID19(),
|
|
19482
20041
|
topicId,
|
|
19483
20042
|
authorId: "system",
|
|
19484
20043
|
text: text2,
|
|
@@ -19500,7 +20059,7 @@ function notifyPlaywrightUnavailable(topicId) {
|
|
|
19500
20059
|
}
|
|
19501
20060
|
function appendAskReplyMessage(topicId, text2, sourceLabel, body, kind, agentType, threadRootId) {
|
|
19502
20061
|
const message = {
|
|
19503
|
-
id:
|
|
20062
|
+
id: randomUUID19(),
|
|
19504
20063
|
topicId,
|
|
19505
20064
|
authorId: "ai",
|
|
19506
20065
|
text: text2,
|
|
@@ -20021,7 +20580,7 @@ function startAiTurn(params) {
|
|
|
20021
20580
|
const askReplySources = params.askReplySources;
|
|
20022
20581
|
const sessionRetried = params._sessionRetried === true;
|
|
20023
20582
|
const emptyResponseRetried = params._emptyResponseRetried === true;
|
|
20024
|
-
const queryId = asTurnId(params._queryId ??
|
|
20583
|
+
const queryId = asTurnId(params._queryId ?? randomUUID19());
|
|
20025
20584
|
const roomId = turnConcurrency === "isolated" ? isolatedTurnRoomId(topicId, queryId) : asTurnSlotKey(topicId);
|
|
20026
20585
|
const currentRuntimeEpoch = getRuntimeTopicEpoch(topic.id);
|
|
20027
20586
|
const runtimeEpoch = params._runtimeEpoch ?? currentRuntimeEpoch;
|
|
@@ -20792,7 +21351,7 @@ function triggerTopicAiTurn(topicId, userId, prompt, agentType, opts) {
|
|
|
20792
21351
|
if (!opts?.silent && !opts?.hideInjectMessage) {
|
|
20793
21352
|
const now = new Date().toISOString();
|
|
20794
21353
|
const injectMsg = {
|
|
20795
|
-
id: opts?.requestId ? `tell-${opts.requestId}` : `tell-${
|
|
21354
|
+
id: opts?.requestId ? `tell-${opts.requestId}` : `tell-${randomUUID19()}`,
|
|
20796
21355
|
topicId,
|
|
20797
21356
|
authorId: opts?.injectAuthorId ?? userId,
|
|
20798
21357
|
sourceAdapter: opts?.injectSourceAdapter,
|
|
@@ -21016,7 +21575,7 @@ var init_auth_check = __esm(async () => {
|
|
|
21016
21575
|
});
|
|
21017
21576
|
|
|
21018
21577
|
// ../../packages/core/src/agents/mcp-tools/spawn-subagent.ts
|
|
21019
|
-
import { z as
|
|
21578
|
+
import { z as z6 } from "zod";
|
|
21020
21579
|
function activeQueryUserId(topic) {
|
|
21021
21580
|
return topic?.participants.find((participant) => hasActiveQuery(participant.userId, topic.id))?.userId;
|
|
21022
21581
|
}
|
|
@@ -21583,7 +22142,7 @@ ${card.task}${reportInstruction}`;
|
|
|
21583
22142
|
name: "start_subagent",
|
|
21584
22143
|
description: "Start a prepared descendant subagent task.",
|
|
21585
22144
|
schema: {
|
|
21586
|
-
topic_id:
|
|
22145
|
+
topic_id: z6.string().describe("Prepared subagent room id.")
|
|
21587
22146
|
},
|
|
21588
22147
|
async handler(input) {
|
|
21589
22148
|
const result = ownedSubagentTree(ctx);
|
|
@@ -21603,7 +22162,7 @@ ${card.task}${reportInstruction}`;
|
|
|
21603
22162
|
name: "delete_subagent",
|
|
21604
22163
|
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.",
|
|
21605
22164
|
schema: {
|
|
21606
|
-
topic_id:
|
|
22165
|
+
topic_id: z6.string().describe("Exact subagent room id returned by list_subagents; names are not accepted.")
|
|
21607
22166
|
},
|
|
21608
22167
|
async handler(input) {
|
|
21609
22168
|
const result = ownedSubagentTree(ctx);
|
|
@@ -21627,8 +22186,8 @@ ${card.task}${reportInstruction}`;
|
|
|
21627
22186
|
name: "grant_subagent_tell",
|
|
21628
22187
|
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.",
|
|
21629
22188
|
schema: {
|
|
21630
|
-
subagent_topic_id:
|
|
21631
|
-
target_topic_id:
|
|
22189
|
+
subagent_topic_id: z6.string().describe("Source descendant subagent topic id."),
|
|
22190
|
+
target_topic_id: z6.string().describe("Target topic id: this manager room or another descendant in its tree.")
|
|
21632
22191
|
},
|
|
21633
22192
|
async handler(input) {
|
|
21634
22193
|
const result = ownedSubagentTree(ctx);
|
|
@@ -21653,8 +22212,8 @@ ${card.task}${reportInstruction}`;
|
|
|
21653
22212
|
name: "revoke_subagent_tell",
|
|
21654
22213
|
description: "Remove an extra tell_session connection previously granted to a descendant subagent.",
|
|
21655
22214
|
schema: {
|
|
21656
|
-
subagent_topic_id:
|
|
21657
|
-
target_topic_id:
|
|
22215
|
+
subagent_topic_id: z6.string().describe("Source descendant subagent topic id."),
|
|
22216
|
+
target_topic_id: z6.string().describe("Granted target topic id.")
|
|
21658
22217
|
},
|
|
21659
22218
|
async handler(input) {
|
|
21660
22219
|
const result = ownedSubagentTree(ctx);
|
|
@@ -21676,18 +22235,18 @@ ${card.task}${reportInstruction}`;
|
|
|
21676
22235
|
}
|
|
21677
22236
|
function createSpawnSubagentToolDefinition(ctx, options = {}) {
|
|
21678
22237
|
const executionOverrideSchema = options.executionOverrides === false ? {} : {
|
|
21679
|
-
agent:
|
|
21680
|
-
model:
|
|
22238
|
+
agent: z6.enum(["claude", "codex", "maestro"]).optional().describe("Agent backend override. Defaults to this room's agent."),
|
|
22239
|
+
model: z6.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.`)
|
|
21681
22240
|
};
|
|
21682
22241
|
return {
|
|
21683
22242
|
name: "spawn_subagent",
|
|
21684
22243
|
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.",
|
|
21685
22244
|
schema: {
|
|
21686
|
-
task:
|
|
21687
|
-
name:
|
|
22245
|
+
task: z6.string().describe("Self-contained task brief. The worker has topic memory but no parent conversation history, so include all task-specific context."),
|
|
22246
|
+
name: z6.string().optional().describe("Short name for the subagent room. Auto-generated when omitted."),
|
|
21688
22247
|
...executionOverrideSchema,
|
|
21689
|
-
memory_topic:
|
|
21690
|
-
report_mode:
|
|
22248
|
+
memory_topic: z6.string().optional().describe("Optional knowledge source: an accessible topic id/title or topic/*.md brief. Defaults to the parent room's effective topic brief."),
|
|
22249
|
+
report_mode: z6.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.")
|
|
21691
22250
|
},
|
|
21692
22251
|
async handler(input) {
|
|
21693
22252
|
return createSubagent(ctx, input, true);
|
|
@@ -21699,14 +22258,14 @@ ${card.task}${reportInstruction}`;
|
|
|
21699
22258
|
name: "create_subagent",
|
|
21700
22259
|
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.",
|
|
21701
22260
|
schema: {
|
|
21702
|
-
task:
|
|
21703
|
-
name:
|
|
22261
|
+
task: z6.string().describe("Self-contained task brief for the subagent."),
|
|
22262
|
+
name: z6.string().optional().describe("Short room name. Auto-generated when omitted."),
|
|
21704
22263
|
...options.executionOverrides === false ? {} : {
|
|
21705
|
-
agent:
|
|
21706
|
-
model:
|
|
22264
|
+
agent: z6.enum(["claude", "codex", "maestro"]).optional(),
|
|
22265
|
+
model: z6.string().optional()
|
|
21707
22266
|
},
|
|
21708
|
-
memory_topic:
|
|
21709
|
-
report_mode:
|
|
22267
|
+
memory_topic: z6.string().optional().describe("Optional accessible topic id/title or topic/*.md brief to use as this worker's knowledge source. Defaults to the parent."),
|
|
22268
|
+
report_mode: z6.enum(["auto", "tell", "status-only"]).optional()
|
|
21710
22269
|
},
|
|
21711
22270
|
async handler(input) {
|
|
21712
22271
|
return createSubagent(ctx, input, false);
|
|
@@ -24636,7 +25195,7 @@ ${renderDecisionList(decisions)}`);
|
|
|
24636
25195
|
}
|
|
24637
25196
|
// ../../packages/core/src/mcp/factories/session-comm.ts
|
|
24638
25197
|
import { McpServer as McpServer4 } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
24639
|
-
import { z as
|
|
25198
|
+
import { z as z7 } from "zod";
|
|
24640
25199
|
|
|
24641
25200
|
// ../../packages/core/src/mcp/session-comm/tell-permissions.ts
|
|
24642
25201
|
await __promiseAll([
|
|
@@ -24677,29 +25236,29 @@ function createSessionCommMcpServer(context, host, options = {}) {
|
|
|
24677
25236
|
const subagentIdentity = resolveSubagentTellIdentity(context.currentTopicId, context.subagentParentTopicId);
|
|
24678
25237
|
server.tool("list_sessions", "List available sessions and remote peer sessions for inter-session communication.", {}, async () => host.listSessions(context));
|
|
24679
25238
|
server.tool("configure_mcp", `Configure optional MCP servers for the current topic. Required: ${required}. Optional: ${optional}.`, {
|
|
24680
|
-
enabled:
|
|
25239
|
+
enabled: z7.array(z7.string()).nullable().optional()
|
|
24681
25240
|
}, async ({ enabled }) => host.configureMcp(context, enabled));
|
|
24682
25241
|
server.tool("get_mcp_config", "Get the current topic MCP configuration.", {}, async () => host.getMcpConfig(context));
|
|
24683
25242
|
if (host.getBrowserProfile) {
|
|
24684
25243
|
server.tool("get_browser_profile", "Get this topic's browser profile and the profiles owned by the same user.", {}, async () => host.getBrowserProfile(context));
|
|
24685
25244
|
}
|
|
24686
25245
|
if (host.setBrowserProfile) {
|
|
24687
|
-
server.tool("set_browser_profile", "Assign this topic to a named shared browser profile. Takes effect next turn.", { profile:
|
|
25246
|
+
server.tool("set_browser_profile", "Assign this topic to a named shared browser profile. Takes effect next turn.", { profile: z7.string() }, async ({ profile }) => host.setBrowserProfile(context, profile));
|
|
24688
25247
|
}
|
|
24689
25248
|
if (host.deleteBrowserProfile) {
|
|
24690
|
-
server.tool("delete_browser_profile", "Delete an unused named browser profile and its on-disk data. The default or an assigned profile cannot be deleted.", { profile:
|
|
25249
|
+
server.tool("delete_browser_profile", "Delete an unused named browser profile and its on-disk data. The default or an assigned profile cannot be deleted.", { profile: z7.string() }, async ({ profile }) => host.deleteBrowserProfile(context, profile));
|
|
24691
25250
|
}
|
|
24692
25251
|
server.tool("peek_session", "Inspect running and idle sessions, including pending ask_session calls.", {}, async () => host.peekSession(context));
|
|
24693
|
-
server.tool("set_description", "Set the current session description used as a routing hint.", { description:
|
|
25252
|
+
server.tool("set_description", "Set the current session description used as a routing hint.", { description: z7.string() }, async ({ description }) => host.setDescription(context, description));
|
|
24694
25253
|
if (!context.replyOnly) {
|
|
24695
25254
|
if (!subagentIdentity.restricted) {
|
|
24696
|
-
server.tool("ask_session", "Ask another local or remote session a question and wait for its answer.", { to:
|
|
24697
|
-
server.tool("ask_cron", "Ask this topic's shared Cron session a question. The answer is injected asynchronously into the current conversation.", { message:
|
|
25255
|
+
server.tool("ask_session", "Ask another local or remote session a question and wait for its answer.", { to: z7.string(), message: z7.string() }, async (input) => host.askSession(context, input));
|
|
25256
|
+
server.tool("ask_cron", "Ask this topic's shared Cron session a question. The answer is injected asynchronously into the current conversation.", { message: z7.string() }, async ({ message }) => host.askCron(context, message));
|
|
24698
25257
|
}
|
|
24699
25258
|
if (!subagentIdentity.restricted) {
|
|
24700
|
-
server.tool("abort_session", "Abort the active query in another session.", { to:
|
|
25259
|
+
server.tool("abort_session", "Abort the active query in another session.", { to: z7.string() }, async ({ to }) => host.abortSession(context, to));
|
|
24701
25260
|
}
|
|
24702
|
-
server.tool("tell_session", "Send a one-way message to another local or remote session.", { to:
|
|
25261
|
+
server.tool("tell_session", "Send a one-way message to another local or remote session.", { to: z7.string(), message: z7.string() }, async (input) => {
|
|
24703
25262
|
if (subagentIdentity.restricted && !canSubagentTellTargetByName(subagentIdentity, input.to)) {
|
|
24704
25263
|
return {
|
|
24705
25264
|
content: [
|
|
@@ -24916,7 +25475,7 @@ function createSystemHealthMcpServer(host = defaultSystemHealthMcpHost) {
|
|
|
24916
25475
|
init_mcp_helpers();
|
|
24917
25476
|
await init_tasks();
|
|
24918
25477
|
import { McpServer as McpServer6 } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
24919
|
-
import { z as
|
|
25478
|
+
import { z as z8 } from "zod";
|
|
24920
25479
|
var defaultTaskMcpHost = {
|
|
24921
25480
|
readTasks,
|
|
24922
25481
|
writeTasks
|
|
@@ -24930,13 +25489,13 @@ function createTaskMcpServer(context, host = defaultTaskMcpHost) {
|
|
|
24930
25489
|
}
|
|
24931
25490
|
return null;
|
|
24932
25491
|
};
|
|
24933
|
-
const statusEnum =
|
|
25492
|
+
const statusEnum = z8.enum(TASK_STATUS_VALUES);
|
|
24934
25493
|
server.tool("task_create", "Add one or more tasks to this topic's shared Otium task list. Use this instead of provider-native todo/task tools.", {
|
|
24935
|
-
tasks:
|
|
24936
|
-
subject:
|
|
24937
|
-
active_form:
|
|
24938
|
-
blocked_by:
|
|
24939
|
-
owner:
|
|
25494
|
+
tasks: z8.array(z8.object({
|
|
25495
|
+
subject: z8.string().min(1).describe("Short imperative task description"),
|
|
25496
|
+
active_form: z8.string().optional().describe("Label to show while the task is in progress"),
|
|
25497
|
+
blocked_by: z8.array(z8.string()).optional().describe("Task ids blocking this task"),
|
|
25498
|
+
owner: z8.string().optional().describe("Owner/agent label for multi-agent work")
|
|
24940
25499
|
})).min(1)
|
|
24941
25500
|
}, async ({ tasks: inputs }) => {
|
|
24942
25501
|
const guard = requireContext();
|
|
@@ -24960,13 +25519,13 @@ ${renderTaskList(tasks2)}`);
|
|
|
24960
25519
|
}
|
|
24961
25520
|
});
|
|
24962
25521
|
server.tool("task_update", "Update task status/content in this topic's shared Otium task list. Batch related status changes in one call.", {
|
|
24963
|
-
updates:
|
|
24964
|
-
id:
|
|
25522
|
+
updates: z8.array(z8.object({
|
|
25523
|
+
id: z8.string().describe("Target task id"),
|
|
24965
25524
|
status: statusEnum.optional().describe("New status"),
|
|
24966
|
-
subject:
|
|
24967
|
-
active_form:
|
|
24968
|
-
blocked_by:
|
|
24969
|
-
owner:
|
|
25525
|
+
subject: z8.string().min(1).optional().describe("Replacement task description"),
|
|
25526
|
+
active_form: z8.string().optional().describe("Replacement in-progress label; empty string clears it"),
|
|
25527
|
+
blocked_by: z8.array(z8.string()).optional().describe("Replacement blocker id list"),
|
|
25528
|
+
owner: z8.string().optional().describe("Replacement owner; empty string clears it")
|
|
24970
25529
|
})).min(1)
|
|
24971
25530
|
}, async ({ updates }) => {
|
|
24972
25531
|
const guard = requireContext();
|
|
@@ -25000,7 +25559,7 @@ Missing ids ignored: ${missing.join(", ")}` : "";
|
|
|
25000
25559
|
return mcpError(`task_list failed: ${errMsg(error)}`);
|
|
25001
25560
|
}
|
|
25002
25561
|
});
|
|
25003
|
-
server.tool("task_get", "Read one task from this topic's shared Otium task list as JSON.", { id:
|
|
25562
|
+
server.tool("task_get", "Read one task from this topic's shared Otium task list as JSON.", { id: z8.string().describe("Task id") }, async ({ id }) => {
|
|
25004
25563
|
const guard = requireContext();
|
|
25005
25564
|
if (guard)
|
|
25006
25565
|
return guard;
|
|
@@ -25014,8 +25573,8 @@ Missing ids ignored: ${missing.join(", ")}` : "";
|
|
|
25014
25573
|
}
|
|
25015
25574
|
});
|
|
25016
25575
|
server.tool("task_delete", "Delete tasks from this topic's shared Otium task list. Use all=true only when starting a new plan.", {
|
|
25017
|
-
ids:
|
|
25018
|
-
all:
|
|
25576
|
+
ids: z8.array(z8.string()).optional().describe("Task ids to delete"),
|
|
25577
|
+
all: z8.boolean().optional().describe("Delete the whole list")
|
|
25019
25578
|
}, async ({ ids, all }) => {
|
|
25020
25579
|
const guard = requireContext();
|
|
25021
25580
|
if (guard)
|
|
@@ -25038,7 +25597,7 @@ ${renderTaskList(tasks2)}`);
|
|
|
25038
25597
|
// ../../packages/core/src/mcp/factories/token-stats.ts
|
|
25039
25598
|
await init_token_stats();
|
|
25040
25599
|
import { McpServer as McpServer7 } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
25041
|
-
import { z as
|
|
25600
|
+
import { z as z9 } from "zod";
|
|
25042
25601
|
var defaultTokenStatsMcpHost = {
|
|
25043
25602
|
getStats,
|
|
25044
25603
|
calcCost
|
|
@@ -25058,9 +25617,9 @@ function createTokenStatsMcpServer(context, host = defaultTokenStatsMcpHost) {
|
|
|
25058
25617
|
"groupBy: 'session'\uC73C\uB85C \uC124\uC815\uD558\uBA74 \uC138\uC158\uBCC4 \uD1A0\uD070 \uC0AC\uC6A9\uB7C9 \uB7AD\uD0B9\uC744 \uBC18\uD658\uD569\uB2C8\uB2E4."
|
|
25059
25618
|
].join(`
|
|
25060
25619
|
`), {
|
|
25061
|
-
from:
|
|
25062
|
-
to:
|
|
25063
|
-
groupBy:
|
|
25620
|
+
from: z9.string().optional().describe("\uC2DC\uC791 \uC2DC\uAC01 ISO 8601. \uC608: '2026-03-28T09:00:00Z'"),
|
|
25621
|
+
to: z9.string().optional().describe("\uC885\uB8CC \uC2DC\uAC01 ISO 8601. \uC0DD\uB7B5 \uC2DC \uD604\uC7AC"),
|
|
25622
|
+
groupBy: z9.enum(["hour", "session"]).optional().describe("\uC9D1\uACC4 \uAE30\uC900. \uAE30\uBCF8\uAC12: hour")
|
|
25064
25623
|
}, async ({ from, to, groupBy = "hour" }) => {
|
|
25065
25624
|
const snapshot = host.getStats(context.userId, from, to);
|
|
25066
25625
|
const {
|
|
@@ -25170,4 +25729,4 @@ export {
|
|
|
25170
25729
|
createAgentHealthMcpServer
|
|
25171
25730
|
};
|
|
25172
25731
|
|
|
25173
|
-
//# debugId=
|
|
25732
|
+
//# debugId=0C619D7E527DC93B64756E2164756E21
|