harness: typing indicator during agent turns (keepalive refreshes 60s window, cleared on reply)

This commit is contained in:
pierre committed 2026-09-05 03:15:58 +03:00
1 parent 2f0890a61c
commit bf40b50f52
2 files changed
+70

No files matched your search

+27
View File
@@ -292,6 +292,33 @@ fn markdownToHtml(alloc: std.mem.Allocator, md: []const u8) ![]u8 {
return out.toOwnedSlice(alloc);
}
/// Typing notification. timeout_ms: how long the indicator lasts server-side
/// (refresh periodically for long turns). typing=false clears it immediately.
pub fn setTyping(alloc: std.mem.Allocator, io: Io, token: []const u8, user_id: []const u8, room: []const u8, typing: bool, timeout_ms: i64) void {
var url_buf: std.ArrayListUnmanaged(u8) = .empty;
defer url_buf.deinit(alloc);
const user_esc = std.fmt.allocPrint(alloc, "{s}", .{user_id}) catch return;
defer alloc.free(user_esc);
// user ids contain chars that are fine unescaped in a path segment for
// conduwuit, but encode the ':' minimally
var user_path: std.ArrayListUnmanaged(u8) = .empty;
defer user_path.deinit(alloc);
for (user_esc) |ch| {
if (ch == ':') user_path.appendSlice(alloc, "%3A") catch return else user_path.append(alloc, ch) catch return;
}
const url = std.fmt.allocPrint(alloc, "{s}/_matrix/client/v3/rooms/{s}/typing/{s}", .{ HOMESERVER, room, user_path.items }) catch return;
defer alloc.free(url);
var payload: std.ArrayListUnmanaged(u8) = .empty;
defer payload.deinit(alloc);
const body = if (typing)
std.fmt.allocPrint(alloc, "{{\"typing\":true,\"timeout\":{d}}}", .{timeout_ms}) catch return
else
alloc.dupe(u8, "{\"typing\":false}") catch return;
defer alloc.free(body);
_ = httpRaw(alloc, io, "PUT", url, token, "application/json", body) catch null;
}
pub fn sendText(alloc: std.mem.Allocator, io: Io, token: []const u8, room: []const u8, body: []const u8) ![]u8 {
const ts = Io.Clock.now(.real, io).nanoseconds;
const url = try std.fmt.allocPrint(alloc, "{s}/_matrix/client/v3/rooms/{s}/send/m.room.message/infr-{d}", .{ HOMESERVER, room, ts });