fix(telegram): break the DI cycle through CalendarScriptEnvironment

ChatRegistry -> JobScheduler -> IScriptEnvironment -> ChatRegistry made the host
fail at startup. CalendarScriptEnvironment now takes TelegramOptions and computes
the workspace with the new static ChatRegistry.WorkspaceFor(options, chatId).
This commit is contained in:
EugeneTes
2026-10-02 08:45:48 +00:00
parent d2fd7ffa02
commit 0cb791d529
3 changed files with 10 additions and 4 deletions

View File

@@ -82,7 +82,11 @@ public sealed class ChatRegistry(
public static string ScopeFor(long chatId) => $"telegram-{chatId}";
public string WorkspaceFor(long chatId) => Path.Combine(options.WorkspaceRoot, chatId.ToString(System.Globalization.CultureInfo.InvariantCulture));
public string WorkspaceFor(long chatId) => WorkspaceFor(options, chatId);
/// <summary><c>&lt;WorkspaceRoot&gt;/&lt;chatId&gt;</c>, for services that can't depend on the registry (it would close a DI cycle).</summary>
public static string WorkspaceFor(TelegramOptions options, long chatId) =>
Path.Combine(options.WorkspaceRoot, chatId.ToString(System.Globalization.CultureInfo.InvariantCulture));
/// <summary>The chat's context if it was already created, without creating it.</summary>
public ChatContext? Find(long chatId) =>

View File

@@ -9,8 +9,10 @@ namespace Volcanic.AgenticCore.Telegram.Scheduling;
/// Gives a chat's watcher scripts <c>GOOGLE_ACCESS_TOKEN</c>: an access token for that chat's calendar, valid for the
/// run's timeout plus a minute (as far as Google's one-hour lifetime allows). The bot refreshes it; the refresh token
/// and the client secret never reach the script. A chat without a connected calendar gets nothing.
/// Takes <see cref="TelegramOptions"/>, not <see cref="ChatRegistry"/>: the registry depends on the scheduler, which
/// depends on this.
/// </summary>
public sealed class CalendarScriptEnvironment(ChatRegistry chats, GoogleCalendarOptions calendar) : IScriptEnvironment
public sealed class CalendarScriptEnvironment(TelegramOptions options, GoogleCalendarOptions calendar) : IScriptEnvironment
{
public const string AccessTokenVariable = "GOOGLE_ACCESS_TOKEN";
private static readonly TimeSpan Margin = TimeSpan.FromMinutes(1);
@@ -20,7 +22,7 @@ public sealed class CalendarScriptEnvironment(ChatRegistry chats, GoogleCalendar
{
if (!long.TryParse(target, NumberStyles.AllowLeadingSign, CultureInfo.InvariantCulture, out var chatId)) return None;
var token = await (calendar.Connections ?? GoogleCalendarConnections.Shared)
.TryGetAccessTokenAsync(calendar, ChatRegistry.ScopeFor(chatId), chats.WorkspaceFor(chatId), timeout + Margin, cancellationToken)
.TryGetAccessTokenAsync(calendar, ChatRegistry.ScopeFor(chatId), ChatRegistry.WorkspaceFor(options, chatId), timeout + Margin, cancellationToken)
.ConfigureAwait(false);
return token is null ? None : new Dictionary<string, string> { [AccessTokenVariable] = token };
}

View File

@@ -166,7 +166,7 @@ public class GoogleCalendarHostTests : IDisposable
{
var calendar = Calendar();
await using var host = new ChatHost(calendar: calendar);
var environment = new Volcanic.AgenticCore.Telegram.Scheduling.CalendarScriptEnvironment(host.Chats, calendar);
var environment = new Volcanic.AgenticCore.Telegram.Scheduling.CalendarScriptEnvironment(host.Options, calendar);
var target = Updates.AllowedUser.ToString(System.Globalization.CultureInfo.InvariantCulture);
Assert.Empty(await environment.ForRunAsync(target, TimeSpan.FromMinutes(5), CancellationToken.None)); // not connected yet