negotium 0.22.3 → 0.22.5

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