matrix bridge: proper JSON sync parsing, backlog forwarding, message provenance
- sync(): std.json walk of rooms.join.*.timeline (old string-window scraper misattributed senders across events); Event gains ts - startup backlog: peer messages from last 30min forwarded to agent as context (reply suppressed); failures logged, never fatal (systemd Restart=on-failure would otherwise re-forward every 3s) - messages to agent prefixed [matrix HH:MMZ] with UTC event timestamp - sendText: jsonEscape covers control chars (\u00XX), markdown -> HTML formatted_body (bold/italic/code/fences) for Element rendering - no startup banner in room; harness now runs as systemd user service matrix-harness (unit + .matrix-env, untracked)
This commit is contained in:
1 parent
77f3150af4
commit
9f1a839a22
2 files changed
+269
-40
No files matched your search
+48
-6
@@ -55,13 +55,42 @@ pub fn main(init: std.process.Init) !void {
|
||||
defer if (owned_room) |r| alloc.free(r);
|
||||
std.debug.print("matrix_harness: room {s}\n", .{room_id});
|
||||
|
||||
_ = try matrix.sendText(alloc, io, session.token, room_id, "matrix_harness online in persistent room. Messages reach the agent, replies come back here.");
|
||||
// No room banner on startup — restarts (deploys, watchdog) would spam
|
||||
// the DM. Startup is logged to journald instead.
|
||||
|
||||
// Establish a sync point (drop history before now).
|
||||
// Establish a sync point. RECENT peer messages (last 30 min only, so
|
||||
// harness restarts don't re-forward ancient history every boot) are
|
||||
// forwarded to the agent as one [matrix backlog] context message
|
||||
// (reply suppressed).
|
||||
const first = try matrix.sync(alloc, io, session.token, "", 0);
|
||||
for (first.events) |ev| {
|
||||
alloc.free(ev.sender);
|
||||
alloc.free(ev.body);
|
||||
{
|
||||
const now_ms: i64 = @intCast(@divTrunc(std.Io.Clock.now(.real, io).nanoseconds, std.time.ns_per_ms));
|
||||
var backlog: std.ArrayListUnmanaged(u8) = .empty;
|
||||
defer backlog.deinit(alloc);
|
||||
for (first.events) |ev| {
|
||||
defer alloc.free(ev.sender);
|
||||
defer alloc.free(ev.body);
|
||||
if (!std.mem.eql(u8, ev.sender, peer)) continue;
|
||||
if (ev.body.len == 0) continue;
|
||||
if (ev.ts > 0 and now_ms - ev.ts > 30 * 60 * 1000) continue; // stale
|
||||
try backlog.appendSlice(alloc, ev.sender);
|
||||
try backlog.appendSlice(alloc, ": ");
|
||||
try backlog.appendSlice(alloc, ev.body);
|
||||
try backlog.append(alloc, '\n');
|
||||
}
|
||||
if (backlog.items.len > 0) {
|
||||
std.debug.print("matrix_harness: forwarding backlog ({d} bytes) to agent\n", .{backlog.items.len});
|
||||
const hist_msg = try std.fmt.allocPrint(alloc, "[matrix backlog — messages received while harness was offline]\n{s}", .{backlog.items});
|
||||
defer alloc.free(hist_msg);
|
||||
// Do NOT exit on backlog failure: with systemd Restart=on-failure
|
||||
// this would re-forward the same backlog every 3s forever.
|
||||
// Log, skip, keep polling.
|
||||
if (letta.infer(io, alloc, agent, hist_msg)) |reply| {
|
||||
alloc.free(reply); // backlog replies are not sent back
|
||||
} else |e| {
|
||||
std.debug.print("matrix_harness: backlog letta failed: {s} (skipping)\n", .{@errorName(e)});
|
||||
}
|
||||
}
|
||||
}
|
||||
alloc.free(first.events);
|
||||
var since = first.next;
|
||||
@@ -86,7 +115,20 @@ pub fn main(init: std.process.Init) !void {
|
||||
|
||||
std.debug.print("matrix_harness: [{s}] {s}\n", .{ ev.sender, ev.body });
|
||||
|
||||
const reply = letta.infer(io, alloc, agent, ev.body) catch |e| {
|
||||
// Prefix with origin + event UTC timestamp so the agent can tell
|
||||
// Matrix-origin messages from CLI ones (and when they were sent).
|
||||
var ts_buf: [16]u8 = undefined;
|
||||
const ts_str: []const u8 = blk: {
|
||||
if (ev.ts <= 0) break :blk "";
|
||||
const secs: u64 = @intCast(@divTrunc(ev.ts, 1000));
|
||||
const epoch = std.time.epoch.EpochSeconds{ .secs = secs };
|
||||
const sod = epoch.getDaySeconds();
|
||||
break :blk std.fmt.bufPrint(&ts_buf, " {d:0>2}:{d:0>2}Z", .{ sod.getHoursIntoDay(), sod.getMinutesIntoHour() }) catch "";
|
||||
};
|
||||
const prefixed = try std.fmt.allocPrint(alloc, "[matrix{s}] {s}", .{ ts_str, ev.body });
|
||||
defer alloc.free(prefixed);
|
||||
|
||||
const reply = letta.infer(io, alloc, agent, prefixed) catch |e| {
|
||||
std.debug.print("matrix_harness: letta failed: {s}\n", .{@errorName(e)});
|
||||
const err_txt = "agent failed — see harness logs";
|
||||
if (matrix.sendText(alloc, io, session.token, room_id, err_txt)) |ev_id| alloc.free(ev_id) else |_| {}
|
||||
|
||||
Reference in new issue
Block a user