70 lines
2.8 KiB
C#
70 lines
2.8 KiB
C#
using System.Collections.Concurrent;
|
|
using System.Text.Json;
|
|
|
|
namespace Volcanic.AgenticCore.Extensions.A2A;
|
|
|
|
public static class WatchSources
|
|
{
|
|
public const string Push = "push";
|
|
public const string Subscribe = "subscribe";
|
|
public const string Poll = "poll";
|
|
}
|
|
|
|
/// <param name="Target">The host's delivery target (Telegram: the chat id).</param>
|
|
/// <param name="PushToken">Our secret for this watch's push webhook; null when not using push.</param>
|
|
/// <param name="Undelivered">A finished result that couldn't be delivered yet (sent on the target's next session start).</param>
|
|
public sealed record WatchRecord(
|
|
string WatchId, string Target, string Agent, string TaskId, string ContextId, string? PushToken,
|
|
DateTimeOffset StartedAt, string Source, string? Undelivered = null);
|
|
|
|
/// <summary><c><workspace>/.volcanic/a2a/pending.json</c>: the watches of a workspace, so they survive restarts.</summary>
|
|
public static class PendingFile
|
|
{
|
|
private static readonly JsonSerializerOptions Json = new(JsonSerializerDefaults.Web) { WriteIndented = true };
|
|
private static readonly ConcurrentDictionary<string, object> Locks = new(StringComparer.Ordinal);
|
|
|
|
public static string PathFor(string workspace) => Path.Combine(workspace, ".volcanic", "a2a", "pending.json");
|
|
|
|
public static IReadOnlyList<WatchRecord> Read(string workspace)
|
|
{
|
|
lock (LockFor(workspace)) return ReadUnlocked(workspace);
|
|
}
|
|
|
|
public static void Upsert(string workspace, WatchRecord record) => Change(workspace, list =>
|
|
{
|
|
list.RemoveAll(r => r.WatchId == record.WatchId);
|
|
list.Add(record);
|
|
});
|
|
|
|
public static void Remove(string workspace, string watchId) => Change(workspace, list => list.RemoveAll(r => r.WatchId == watchId));
|
|
|
|
private static void Change(string workspace, Action<List<WatchRecord>> change)
|
|
{
|
|
lock (LockFor(workspace))
|
|
{
|
|
var list = ReadUnlocked(workspace).ToList();
|
|
change(list);
|
|
var path = PathFor(workspace);
|
|
Directory.CreateDirectory(Path.GetDirectoryName(path)!);
|
|
var temp = path + "." + Guid.NewGuid().ToString("N") + ".tmp";
|
|
File.WriteAllText(temp, JsonSerializer.Serialize(list, Json));
|
|
File.Move(temp, path, overwrite: true);
|
|
}
|
|
}
|
|
|
|
private static List<WatchRecord> ReadUnlocked(string workspace)
|
|
{
|
|
var path = PathFor(workspace);
|
|
try
|
|
{
|
|
return File.Exists(path) ? JsonSerializer.Deserialize<List<WatchRecord>>(File.ReadAllText(path), Json) ?? [] : [];
|
|
}
|
|
catch (Exception ex) when (ex is JsonException or IOException or UnauthorizedAccessException)
|
|
{
|
|
return [];
|
|
}
|
|
}
|
|
|
|
private static object LockFor(string workspace) => Locks.GetOrAdd(Path.GetFullPath(workspace), _ => new object());
|
|
}
|