agent-relay-server 0.120.4 → 0.120.6

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 (109) hide show
  1. package/docs/openapi.json +1 -1
  2. package/package.json +3 -3
  3. package/public/assets/{MessageBubble-Cqi3XsZ7.js → MessageBubble-So86mU9-.js} +2 -2
  4. package/public/assets/{MessageBubble-Cqi3XsZ7.js.map → MessageBubble-So86mU9-.js.map} +1 -1
  5. package/public/assets/{PlanGraphPanel-D6Um05oW.js → PlanGraphPanel-DBZYAsww.js} +2 -2
  6. package/public/assets/{PlanGraphPanel-D6Um05oW.js.map → PlanGraphPanel-DBZYAsww.js.map} +1 -1
  7. package/public/assets/{activity-B0LsQHEz.js → activity-CZNwmXg1.js} +2 -2
  8. package/public/assets/{activity-B0LsQHEz.js.map → activity-CZNwmXg1.js.map} +1 -1
  9. package/public/assets/{agent-profiles-O--pe5Ic.js → agent-profiles-BhadoGwi.js} +2 -2
  10. package/public/assets/{agent-profiles-O--pe5Ic.js.map → agent-profiles-BhadoGwi.js.map} +1 -1
  11. package/public/assets/agents-DWn1xx9x.js +2 -0
  12. package/public/assets/{agents-Ii6XlVwR.js.map → agents-DWn1xx9x.js.map} +1 -1
  13. package/public/assets/{analytics-BCzdYWRT.js → analytics-DifGnk7q.js} +2 -2
  14. package/public/assets/{analytics-BCzdYWRT.js.map → analytics-DifGnk7q.js.map} +1 -1
  15. package/public/assets/{automation-Crr3Xzui.js → automation-BExrtKkb.js} +2 -2
  16. package/public/assets/{automation-Crr3Xzui.js.map → automation-BExrtKkb.js.map} +1 -1
  17. package/public/assets/{branch-state-badge-Bhw2rloy.js → branch-state-badge-CyC5Qrhc.js} +2 -2
  18. package/public/assets/{branch-state-badge-Bhw2rloy.js.map → branch-state-badge-CyC5Qrhc.js.map} +1 -1
  19. package/public/assets/{channels-CKs9rvsn.js → channels-BLkdt2XR.js} +2 -2
  20. package/public/assets/{channels-CKs9rvsn.js.map → channels-BLkdt2XR.js.map} +1 -1
  21. package/public/assets/chat-C7OhKtr6.js +2 -0
  22. package/public/assets/{chat-BCwEUj5R.js.map → chat-C7OhKtr6.js.map} +1 -1
  23. package/public/assets/{connectors-D_kQR_vw.js → connectors-C0n12Je8.js} +2 -2
  24. package/public/assets/{connectors-D_kQR_vw.js.map → connectors-C0n12Je8.js.map} +1 -1
  25. package/public/assets/{formatted-body-q_qzt1L-.js → formatted-body-BCQT2qSb.js} +3 -3
  26. package/public/assets/{formatted-body-q_qzt1L-.js.map → formatted-body-BCQT2qSb.js.map} +1 -1
  27. package/public/assets/{formatted-body-impl-CSRGuMcM.js → formatted-body-impl-Bk7wnESl.js} +2 -2
  28. package/public/assets/{formatted-body-impl-CSRGuMcM.js.map → formatted-body-impl-Bk7wnESl.js.map} +1 -1
  29. package/public/assets/index-CRraCeo_.js +25 -0
  30. package/public/assets/index-CRraCeo_.js.map +1 -0
  31. package/public/assets/{insights-DUhXqZTZ.js → insights-DEgES6jz.js} +2 -2
  32. package/public/assets/{insights-DUhXqZTZ.js.map → insights-DEgES6jz.js.map} +1 -1
  33. package/public/assets/{integrations-lbg5CjtE.js → integrations-v14ehskT.js} +2 -2
  34. package/public/assets/{integrations-lbg5CjtE.js.map → integrations-v14ehskT.js.map} +1 -1
  35. package/public/assets/{maintenance-CQgneSVf.js → maintenance-BgVB6_gO.js} +2 -2
  36. package/public/assets/{maintenance-CQgneSVf.js.map → maintenance-BgVB6_gO.js.map} +1 -1
  37. package/public/assets/{managed-agents-DsnYf6fm.js → managed-agents-8_NKSkhI.js} +3 -3
  38. package/public/assets/{managed-agents-DsnYf6fm.js.map → managed-agents-8_NKSkhI.js.map} +1 -1
  39. package/public/assets/{markdown-preview-impl-C1ZtGJp6.js → markdown-preview-impl-yxQkrd2M.js} +2 -2
  40. package/public/assets/{markdown-preview-impl-C1ZtGJp6.js.map → markdown-preview-impl-yxQkrd2M.js.map} +1 -1
  41. package/public/assets/{memory-SSLUTOI2.js → memory-CJD8eekg.js} +2 -2
  42. package/public/assets/{memory-SSLUTOI2.js.map → memory-CJD8eekg.js.map} +1 -1
  43. package/public/assets/{messages-BY26OP4K.js → messages-DLHMZbHZ.js} +2 -2
  44. package/public/assets/{messages-BY26OP4K.js.map → messages-DLHMZbHZ.js.map} +1 -1
  45. package/public/assets/{orchestrators-OY7yPzVI.js → orchestrators-DEOep7FX.js} +2 -2
  46. package/public/assets/{orchestrators-OY7yPzVI.js.map → orchestrators-DEOep7FX.js.map} +1 -1
  47. package/public/assets/{overview-Dj0u1tPG.js → overview-BgpfO6mt.js} +2 -2
  48. package/public/assets/{overview-Dj0u1tPG.js.map → overview-BgpfO6mt.js.map} +1 -1
  49. package/public/assets/{pairs-CrUR50ou.js → pairs-8NVKHh96.js} +2 -2
  50. package/public/assets/{pairs-CrUR50ou.js.map → pairs-8NVKHh96.js.map} +1 -1
  51. package/public/assets/{projects-B_AtxHtH.js → projects-CZJW35C7.js} +2 -2
  52. package/public/assets/{projects-B_AtxHtH.js.map → projects-CZJW35C7.js.map} +1 -1
  53. package/public/assets/{prompt-settings-CPtYSban.js → prompt-settings-fqyLwUa-.js} +2 -2
  54. package/public/assets/{prompt-settings-CPtYSban.js.map → prompt-settings-fqyLwUa-.js.map} +1 -1
  55. package/public/assets/{registry-BgUALWn5.js → registry-BKbLL4mO.js} +2 -2
  56. package/public/assets/{registry-BgUALWn5.js.map → registry-BKbLL4mO.js.map} +1 -1
  57. package/public/assets/{security-5lHfGk7g.js → security-BlTFTbq0.js} +2 -2
  58. package/public/assets/{security-5lHfGk7g.js.map → security-BlTFTbq0.js.map} +1 -1
  59. package/public/assets/{select-Cgx4QSLj.js → select-Zd_qh0uU.js} +2 -2
  60. package/public/assets/{select-Cgx4QSLj.js.map → select-Zd_qh0uU.js.map} +1 -1
  61. package/public/assets/{settings-DFGgxyun.js → settings-nHuSVRkL.js} +2 -2
  62. package/public/assets/{settings-DFGgxyun.js.map → settings-nHuSVRkL.js.map} +1 -1
  63. package/public/assets/store-nXGxgz2Q.js +9 -0
  64. package/public/assets/store-nXGxgz2Q.js.map +1 -0
  65. package/public/assets/{tasks-BvH5rluu.js → tasks-C3JhQ8vB.js} +2 -2
  66. package/public/assets/{tasks-BvH5rluu.js.map → tasks-C3JhQ8vB.js.map} +1 -1
  67. package/public/assets/{teams-VZOIOdoE.js → teams-C9E9Yt39.js} +2 -2
  68. package/public/assets/{teams-VZOIOdoE.js.map → teams-C9E9Yt39.js.map} +1 -1
  69. package/public/assets/{terminal-viewer-impl-DYY3Wmoe.js → terminal-viewer-impl-FP0JlsvS.js} +22 -22
  70. package/public/assets/{terminal-viewer-impl-DYY3Wmoe.js.map → terminal-viewer-impl-FP0JlsvS.js.map} +1 -1
  71. package/public/assets/types-uVOXgvMT.js.map +1 -1
  72. package/public/assets/{work-queue-B6Xk7QNe.js → work-queue-Ema7Hy3s.js} +2 -2
  73. package/public/assets/{work-queue-B6Xk7QNe.js.map → work-queue-Ema7Hy3s.js.map} +1 -1
  74. package/public/assets/{workspaces-CPIzAHAe.js → workspaces-DI317fMk.js} +2 -2
  75. package/public/assets/{workspaces-CPIzAHAe.js.map → workspaces-DI317fMk.js.map} +1 -1
  76. package/public/index.html +3 -3
  77. package/runner/plugins/claude/.claude-plugin/plugin.json +1 -1
  78. package/src/auto-spawn-dispatch.ts +2 -2
  79. package/src/commands-db.ts +14 -0
  80. package/src/db/delivery.ts +85 -3
  81. package/src/db/index.ts +2 -0
  82. package/src/db/migrations.ts +2 -1
  83. package/src/db/provisioning-callmux.ts +180 -0
  84. package/src/db/provisioning-registry.ts +45 -374
  85. package/src/db/provisioning-resolve.ts +245 -0
  86. package/src/db/stats.ts +10 -8
  87. package/src/db/task-entity.ts +7 -8
  88. package/src/lifecycle-manager.ts +11 -12
  89. package/src/maintenance/jobs.ts +3 -1
  90. package/src/maintenance/workspaces.ts +28 -47
  91. package/src/managed-policy-escalation.ts +47 -0
  92. package/src/mcp-task-tools.ts +4 -5
  93. package/src/routes/commands.ts +8 -245
  94. package/src/routes/tasks.ts +3 -5
  95. package/src/routes/workspaces.ts +58 -22
  96. package/src/services/issue-lifecycle.ts +23 -8
  97. package/src/services/task-entity.ts +54 -2
  98. package/src/task-dispatcher.ts +12 -3
  99. package/src/workspace-auto-merge.ts +94 -34
  100. package/src/workspace-command-settle.ts +227 -0
  101. package/src/workspace-escalation.ts +45 -0
  102. package/src/workspace-merge-progress.ts +16 -0
  103. package/src/workspace-orphans.ts +96 -4
  104. package/public/assets/agents-Ii6XlVwR.js +0 -2
  105. package/public/assets/chat-BCwEUj5R.js +0 -2
  106. package/public/assets/index-Ceiwd02X.js +0 -25
  107. package/public/assets/index-Ceiwd02X.js.map +0 -1
  108. package/public/assets/store-BM4zM89B.js +0 -9
  109. package/public/assets/store-BM4zM89B.js.map +0 -1
@@ -474,13 +474,95 @@ export function resolveQueuedDirectMessages(agentId: string): Message[] {
474
474
  * message that died without ever reaching its recipient — as a health signal.
475
475
  */
476
476
  export function countExpiredUndeliveredMessages(now: number = Date.now(), windowMs = 86_400_000): number {
477
- return (getDb().query(`
478
- SELECT COUNT(*) as c FROM messages
477
+ return summarizeExpiredUndeliveredMessages(now, windowMs).unexplainedCount;
478
+ }
479
+
480
+ interface ExpiredUndeliveredRow {
481
+ id: number;
482
+ toTarget: string;
483
+ kind: string;
484
+ subject?: string | null;
485
+ policyValue?: string | null;
486
+ policyStatus?: string | null;
487
+ policyAgentId?: string | null;
488
+ policyLastError?: string | null;
489
+ }
490
+
491
+ export interface ExpiredUndeliveredMessagesSummary {
492
+ totalCount: number;
493
+ unexplainedCount: number;
494
+ explainedManagedPolicyCount: number;
495
+ subjects: NonNullable<HealthCheck["subjects"]>;
496
+ }
497
+
498
+ function managedPolicyExpiryExplanation(row: ExpiredUndeliveredRow): string | undefined {
499
+ if (!row.toTarget.startsWith("policy:")) return undefined;
500
+ if (typeof row.policyValue !== "string") return undefined;
501
+ const policy = parseJson<Record<string, unknown>>(row.policyValue, {});
502
+ if (policy.enabled === false) return "managed policy is disabled";
503
+ if (row.policyStatus === "backoff" || row.policyStatus === "failed") {
504
+ return `managed policy is ${row.policyStatus}${row.policyLastError ? `: ${row.policyLastError}` : ""}`;
505
+ }
506
+ return undefined;
507
+ }
508
+
509
+ /**
510
+ * Classify queued-message expiries that happened within the trailing window.
511
+ * Channel/direct/unknown expiries remain a silent-loss health warning. Managed
512
+ * policy expiries with an explicit unavailable policy state are explained by the
513
+ * policy health surface instead, so they stay visible without degrading global
514
+ * health as generic message loss (#998).
515
+ */
516
+ export function summarizeExpiredUndeliveredMessages(now: number = Date.now(), windowMs = 86_400_000): ExpiredUndeliveredMessagesSummary {
517
+ const rows = getDb().query(`
518
+ SELECT
519
+ m.id,
520
+ m.to_target AS toTarget,
521
+ m.kind,
522
+ m.subject,
523
+ c.value AS policyValue,
524
+ s.status AS policyStatus,
525
+ s.agent_id AS policyAgentId,
526
+ s.last_error AS policyLastError
527
+ FROM messages m
528
+ LEFT JOIN config c
529
+ ON c.namespace = 'spawn-policy'
530
+ AND c.key = substr(m.to_target, 8)
531
+ LEFT JOIN managed_agent_state s
532
+ ON s.policy_name = substr(m.to_target, 8)
479
533
  WHERE delivery_status = 'dead'
480
534
  AND delivery_poison_reason = 'queued message expired'
481
535
  AND delivery_updated_at IS NOT NULL
482
536
  AND delivery_updated_at >= ?
483
- `).get(now - windowMs) as { c: number }).c;
537
+ `).all(now - windowMs) as ExpiredUndeliveredRow[];
538
+
539
+ let unexplainedCount = 0;
540
+ let explainedManagedPolicyCount = 0;
541
+ const explainedByTarget = new Map<string, { count: number; detail: string }>();
542
+
543
+ for (const row of rows) {
544
+ const explanation = managedPolicyExpiryExplanation(row);
545
+ if (!explanation) {
546
+ unexplainedCount += 1;
547
+ continue;
548
+ }
549
+ explainedManagedPolicyCount += 1;
550
+ const existing = explainedByTarget.get(row.toTarget);
551
+ if (existing) existing.count += 1;
552
+ else explainedByTarget.set(row.toTarget, { count: 1, detail: explanation });
553
+ }
554
+
555
+ return {
556
+ totalCount: rows.length,
557
+ unexplainedCount,
558
+ explainedManagedPolicyCount,
559
+ subjects: [...explainedByTarget.entries()].slice(0, 10).map(([target, item]) => ({
560
+ id: target,
561
+ label: target,
562
+ status: "explained",
563
+ detail: `${item.count} expired message(s); ${item.detail}`,
564
+ })),
565
+ };
484
566
  }
485
567
 
486
568
  export function expireQueuedMessages(now: number = Date.now()): Message[] {
package/src/db/index.ts CHANGED
@@ -39,6 +39,8 @@ export * from "./blackboard.ts";
39
39
  export * from "./projects.ts";
40
40
  export * from "./project-entries.ts";
41
41
  export * from "./provisioning-registry.ts";
42
+ export * from "./provisioning-resolve.ts";
43
+ export * from "./provisioning-callmux.ts";
42
44
  export * from "./provider-quotas.ts";
43
45
  export * from "./provider-quota-leases.ts";
44
46
  export * from "./scheduled-timers.ts";
@@ -22,7 +22,8 @@ import { ensureDriveLeaseSchema } from "./drive-leases.ts";
22
22
  import { ensureContinuationArchiveSearchSchema } from "./continuation-archives.ts";
23
23
  import { ensureCrashReportsSchema } from "./crash-reports.ts";
24
24
  import { ensureTerminalEventsSchema } from "./terminal-events.ts";
25
- import { seedBundledProvisioningAssets, seedSharedCallmuxProvisioningAssets } from "./provisioning-registry.ts";
25
+ import { seedBundledProvisioningAssets } from "./provisioning-registry.ts";
26
+ import { seedSharedCallmuxProvisioningAssets } from "./provisioning-callmux.ts";
26
27
  import { provisioningAssetsColumnNames, provisioningAssetsNeedsRebuild, rebuildProvisioningAssetsTable } from "./provisioning-assets-migration.ts";
27
28
  import { normalizeReactionEmoji } from "./mappers.ts";
28
29
  import { normalizeProviderQuotaAccountKeys } from "./provider-quotas.ts";
@@ -0,0 +1,180 @@
1
+ // Shared-callmux registry seeding (#849/#871) — split out of provisioning-registry.ts
2
+ // under the file-size ratchet (epic #291). Reads the host callmux source config,
3
+ // scrubs secrets into env placeholders (literal non-secret values survive only via
4
+ // the per-server configEnv allowlist), and seeds/updates the relay-owned registry
5
+ // rows for the shared callmux MCP servers. The validation invariants the scrub must
6
+ // satisfy (assertConfigEnv / assertCallmuxServerPlaceholders + the allowlist) stay
7
+ // in provisioning-registry.ts beside the definition validator that enforces them.
8
+
9
+ import { existsSync, readFileSync } from "node:fs";
10
+ import { homedir } from "node:os";
11
+ import { join } from "node:path";
12
+ import { errMessage, isRecord } from "agent-relay-sdk";
13
+ import type { ProvisioningAsset, ProvisioningMcpServer } from "agent-relay-sdk";
14
+ import { getDb, ValidationError } from "./connection.ts";
15
+ import { parseJson } from "../utils";
16
+ import {
17
+ isEnvPlaceholder,
18
+ SHARED_CALLMUX_CONFIG_ENV_ALLOWLIST,
19
+ upsertProvisioningAsset,
20
+ } from "./provisioning-registry.ts";
21
+ import { stringRecord } from "./provisioning-resolve.ts";
22
+
23
+ const HOST_CALLMUX_SOURCE_TYPE = "host-callmux";
24
+ export const SHARED_CALLMUX_SEED_SERVER_NAMES = ["tokenlean", "github", "vent", "reasoning", "scribe", "searxng", "qmd"] as const;
25
+ export const SHARED_CALLMUX_SOURCE_CONFIG_ENV = "AGENT_RELAY_SHARED_CALLMUX_SOURCE_CONFIG";
26
+ // Servers seeded from the host callmux config but NOT eligible for the shared listener (deno not provisioned, etc.).
27
+ const SHARED_CALLMUX_SEED_NON_LISTENER_NAMES: ReadonlySet<string> = new Set(["scribe"]);
28
+
29
+ export interface SharedCallmuxSeedResult {
30
+ sourceConfigPath: string;
31
+ seeded: string[];
32
+ skipped: Array<{ name: string; reason: string }>;
33
+ }
34
+
35
+ export function sharedCallmuxSourceConfigPath(env: Record<string, string | undefined> = process.env): string {
36
+ return env[SHARED_CALLMUX_SOURCE_CONFIG_ENV] || env.CALLMUX_CONFIG || join(homedir(), ".config", "callmux", "config.json");
37
+ }
38
+
39
+ function cloneJsonRecord(value: Record<string, unknown>): Record<string, unknown> {
40
+ return JSON.parse(JSON.stringify(value)) as Record<string, unknown>;
41
+ }
42
+
43
+ function scrubEnvPlaceholders(serverName: string, value: unknown): { env: Record<string, string> | undefined; configEnv: Record<string, string> | undefined } {
44
+ if (!isRecord(value)) return { env: undefined, configEnv: undefined };
45
+ const env: Record<string, string> = {};
46
+ const configEnv: Record<string, string> = {};
47
+ for (const [key, raw] of Object.entries(value)) {
48
+ if (typeof raw !== "string") continue;
49
+ if (isEnvPlaceholder(raw)) {
50
+ env[key] = raw;
51
+ } else if (SHARED_CALLMUX_CONFIG_ENV_ALLOWLIST[serverName]?.includes(key)) {
52
+ configEnv[key] = raw;
53
+ } else {
54
+ env[key] = `$${key}`;
55
+ }
56
+ }
57
+ return {
58
+ env: Object.keys(env).length ? env : undefined,
59
+ configEnv: Object.keys(configEnv).length ? configEnv : undefined,
60
+ };
61
+ }
62
+
63
+ function envPlaceholderForLiteral(value: string, env: Record<string, string | undefined>): string | undefined {
64
+ for (const [key, candidate] of Object.entries(env)) {
65
+ if (!candidate || candidate !== value || !/^[A-Z_][A-Z0-9_]*$/.test(key)) continue;
66
+ return `$${key}`;
67
+ }
68
+ return undefined;
69
+ }
70
+
71
+ function envPlaceholderName(value: string): string | undefined {
72
+ const match = value.match(/^\$?\{?([A-Z_][A-Z0-9_]*)\}?$/);
73
+ return match?.[1];
74
+ }
75
+
76
+ function scrubHeaderPlaceholders(value: unknown, env: Record<string, string | undefined>): Record<string, string> | undefined {
77
+ if (!isRecord(value)) return undefined;
78
+ const headers: Record<string, string> = {};
79
+ for (const [key, raw] of Object.entries(value)) {
80
+ if (typeof raw !== "string") continue;
81
+ if (!/authorization|token|secret|key|api/i.test(key) || isEnvPlaceholder(raw) || /^Bearer \$\{[A-Z_][A-Z0-9_]*\}$/.test(raw)) {
82
+ headers[key] = raw;
83
+ continue;
84
+ }
85
+ const bearer = raw.match(/^Bearer\s+(.+)$/i);
86
+ const bearerValue = bearer?.[1];
87
+ const bearerPlaceholderName = bearerValue ? envPlaceholderName(bearerValue) : undefined;
88
+ if (bearerPlaceholderName) {
89
+ headers[key] = `Bearer \${${bearerPlaceholderName}}`;
90
+ continue;
91
+ }
92
+ const placeholder = envPlaceholderForLiteral(bearerValue ?? raw, env);
93
+ if (placeholder) headers[key] = bearer ? `Bearer ${placeholder.startsWith("${") ? placeholder : `\${${placeholder.slice(1)}}`}` : placeholder;
94
+ }
95
+ return Object.keys(headers).length ? headers : undefined;
96
+ }
97
+
98
+ function scrubCallmuxServerSecrets(serverName: string, server: Record<string, unknown>, env: Record<string, string | undefined>): Record<string, unknown> {
99
+ const scrubbed = cloneJsonRecord(server);
100
+ const { env: scrubbedEnv, configEnv } = scrubEnvPlaceholders(serverName, scrubbed.env);
101
+ const scrubbedHeaders = scrubHeaderPlaceholders(scrubbed.headers, env);
102
+ if (scrubbedEnv !== undefined) scrubbed.env = scrubbedEnv;
103
+ else delete scrubbed.env;
104
+ if (configEnv !== undefined) scrubbed.configEnv = configEnv;
105
+ else delete scrubbed.configEnv;
106
+ if (scrubbedHeaders) scrubbed.headers = scrubbedHeaders;
107
+ else delete scrubbed.headers;
108
+ return scrubbed;
109
+ }
110
+
111
+ function mcpServerFromCallmuxServer(server: Record<string, unknown>): ProvisioningMcpServer {
112
+ const projected: ProvisioningMcpServer = {};
113
+ if (typeof server.command === "string") {
114
+ projected.type = "stdio";
115
+ projected.command = server.command;
116
+ if (Array.isArray(server.args)) projected.args = server.args.filter((arg): arg is string => typeof arg === "string");
117
+ if (isRecord(server.env)) projected.env = stringRecord(server.env);
118
+ if (isRecord(server.configEnv)) projected.configEnv = stringRecord(server.configEnv);
119
+ } else if (typeof server.url === "string") {
120
+ projected.type = server.transport === "sse" ? "sse" : "http";
121
+ projected.url = server.url;
122
+ if (isRecord(server.headers)) projected.headers = stringRecord(server.headers);
123
+ if (isRecord(server.configEnv)) projected.configEnv = stringRecord(server.configEnv);
124
+ }
125
+ return projected;
126
+ }
127
+
128
+ function updateSharedCallmuxAssetMetadata(asset: ProvisioningAsset, sourceConfigPath: string, callmuxServer: Record<string, unknown>, sharedListenerEligible = true): void {
129
+ if (!asset.capabilityId) throw new ValidationError("shared callmux seed missing capability id");
130
+ const now = Date.now();
131
+ const provenance = {
132
+ importedBy: "agent-relay-shared-callmux-seed",
133
+ sharedListenerEligible,
134
+ callmux: { sourceConfigPath, serverName: asset.name },
135
+ };
136
+ getDb().query(`
137
+ UPDATE provisioning_assets
138
+ SET source_type = ?, source_ref = ?, provenance = ?, updated_at = ?
139
+ WHERE id = ?
140
+ `).run(HOST_CALLMUX_SOURCE_TYPE, sourceConfigPath, JSON.stringify(provenance), now, asset.id);
141
+ getDb().query("UPDATE provisioning_capabilities SET title = ?, description = ?, updated_at = ? WHERE id = ?")
142
+ .run(asset.name, `Host callmux MCP server ${asset.name}`, now, asset.capabilityId);
143
+ }
144
+
145
+ export function seedSharedCallmuxProvisioningAssets(
146
+ input: { sourceConfigPath?: string; env?: Record<string, string | undefined> } = {},
147
+ ): SharedCallmuxSeedResult {
148
+ const sourceConfigPath = input.sourceConfigPath ?? sharedCallmuxSourceConfigPath(input.env);
149
+ const result: SharedCallmuxSeedResult = { sourceConfigPath, seeded: [], skipped: [] };
150
+ if (!existsSync(sourceConfigPath)) return result;
151
+ const source = parseJson(readFileSync(sourceConfigPath, "utf8"), {}) as unknown;
152
+ const sourceServers = isRecord(source) && isRecord(source.servers) ? source.servers : {};
153
+ for (const name of SHARED_CALLMUX_SEED_SERVER_NAMES) {
154
+ const rawServer = sourceServers[name];
155
+ if (!isRecord(rawServer)) {
156
+ result.skipped.push({ name, reason: "missing" });
157
+ continue;
158
+ }
159
+ const callmuxServer = scrubCallmuxServerSecrets(name, rawServer, input.env ?? process.env);
160
+ const server = mcpServerFromCallmuxServer(callmuxServer);
161
+ try {
162
+ const sharedListenerEligible = !SHARED_CALLMUX_SEED_NON_LISTENER_NAMES.has(name);
163
+ const asset = upsertProvisioningAsset({
164
+ kind: "mcp",
165
+ name,
166
+ definition: {
167
+ kind: "mcp",
168
+ name,
169
+ server,
170
+ metadata: { sharedListenerEligible, callmuxServer },
171
+ },
172
+ });
173
+ updateSharedCallmuxAssetMetadata(asset, sourceConfigPath, callmuxServer, sharedListenerEligible);
174
+ result.seeded.push(name);
175
+ } catch (err) {
176
+ result.skipped.push({ name, reason: errMessage(err) });
177
+ }
178
+ }
179
+ return result;
180
+ }