118 lines
5.8 KiB
Zig
118 lines
5.8 KiB
Zig
const std = @import("std");
|
|
const session_mod = @import("session.zig");
|
|
|
|
// Compact local context formatting for same-user tools/agents.
|
|
// req: session/002, governance/002, governance/003, testing/001, testing/002
|
|
|
|
test {
|
|
_ = sessionContextAlloc;
|
|
}
|
|
|
|
pub const Limits = struct {
|
|
max_bytes_preview: usize = 48,
|
|
max_panel_summary: usize = 96,
|
|
max_task: usize = 96,
|
|
};
|
|
|
|
pub fn localContextAlloc(allocator: std.mem.Allocator, cwd: []const u8, task: ?[]const u8, intent: ?[]const u8, limits: Limits) ![]u8 {
|
|
var out = std.ArrayList(u8).empty;
|
|
errdefer out.deinit(allocator);
|
|
try appendLine(allocator, &out, "context:v1");
|
|
try appendField(allocator, &out, "cwd", cwd, limits.max_panel_summary);
|
|
if (task) |value| try appendField(allocator, &out, "task", value, limits.max_task);
|
|
if (intent) |value| try appendField(allocator, &out, "intent", value, limits.max_task);
|
|
return out.toOwnedSlice(allocator);
|
|
}
|
|
|
|
pub fn sessionContextAlloc(allocator: std.mem.Allocator, session: *const session_mod.Session, task: ?[]const u8, intent: ?[]const u8, limits: Limits) ![]u8 {
|
|
const snap = try session.snapshot();
|
|
const panel_path = try session.panelPathAlloc(allocator);
|
|
defer allocator.free(panel_path);
|
|
const panel_summary = try session.panelSummaryAlloc(allocator);
|
|
defer allocator.free(panel_summary);
|
|
|
|
var out = std.ArrayList(u8).empty;
|
|
errdefer out.deinit(allocator);
|
|
try appendLine(allocator, &out, "context:v1");
|
|
try appendNumber(allocator, &out, "cursor_byte", snap.cursor_byte);
|
|
try appendNumber(allocator, &out, "cursor_cell", snap.cursor_cell);
|
|
try appendNumber(allocator, &out, "bytes_len", snap.bytes.len);
|
|
try appendNumber(allocator, &out, "panel_depth", snap.panel_depth);
|
|
try appendField(allocator, &out, "active_panel", if (snap.active_panel_title) |title| title else "-", limits.max_panel_summary);
|
|
try appendField(allocator, &out, "panel_path", panel_path, limits.max_panel_summary);
|
|
try appendField(allocator, &out, "panel_summary", panel_summary, limits.max_panel_summary);
|
|
try appendField(allocator, &out, "buffer_preview", snap.bytes[0..@min(snap.bytes.len, limits.max_bytes_preview)], limits.max_bytes_preview);
|
|
if (task) |value| try appendField(allocator, &out, "task", value, limits.max_task);
|
|
if (intent) |value| try appendField(allocator, &out, "intent", value, limits.max_task);
|
|
return out.toOwnedSlice(allocator);
|
|
}
|
|
|
|
fn appendNumber(allocator: std.mem.Allocator, out: *std.ArrayList(u8), key: []const u8, value: usize) !void {
|
|
const line = try std.fmt.allocPrint(allocator, "{s}={d}", .{ key, value });
|
|
defer allocator.free(line);
|
|
try appendLine(allocator, out, line);
|
|
}
|
|
|
|
fn appendField(allocator: std.mem.Allocator, out: *std.ArrayList(u8), key: []const u8, value: []const u8, max_len: usize) !void {
|
|
const clean = try sanitizeAlloc(allocator, value, max_len);
|
|
defer allocator.free(clean);
|
|
const line = try std.fmt.allocPrint(allocator, "{s}={s}", .{ key, clean });
|
|
defer allocator.free(line);
|
|
try appendLine(allocator, out, line);
|
|
}
|
|
|
|
fn appendLine(allocator: std.mem.Allocator, out: *std.ArrayList(u8), line: []const u8) !void {
|
|
try out.appendSlice(allocator, line);
|
|
try out.append(allocator, '\n');
|
|
}
|
|
|
|
fn sanitizeAlloc(allocator: std.mem.Allocator, value: []const u8, max_len: usize) ![]u8 {
|
|
var out = std.ArrayList(u8).empty;
|
|
errdefer out.deinit(allocator);
|
|
var i: usize = 0;
|
|
while (i < value.len and out.items.len < max_len) {
|
|
const len = std.unicode.utf8ByteSequenceLength(value[i]) catch 1;
|
|
const end = @min(value.len, i + len);
|
|
const slice = value[i..end];
|
|
if (slice.len == 1 and (slice[0] <= 0x20 or slice[0] == '|')) {
|
|
try out.append(allocator, '_');
|
|
} else {
|
|
try out.appendSlice(allocator, slice);
|
|
}
|
|
i = end;
|
|
}
|
|
if (i < value.len) try out.appendSlice(allocator, "...");
|
|
if (out.items.len == 0) try out.append(allocator, '-');
|
|
return out.toOwnedSlice(allocator);
|
|
}
|
|
|
|
test "regular: session context includes compact editor and panel state" {
|
|
var session = session_mod.Session.init(std.testing.allocator);
|
|
defer session.deinit();
|
|
try session.openFixture("pub const x = 1;\n");
|
|
try session.openListPanel("diagnostics", &.{ "lsp:diagnostics:count_1", "lsp:diag:error:1:5:file:bad" });
|
|
|
|
const text = try sessionContextAlloc(std.testing.allocator, &session, "fix bug", "inspect", .{ .max_bytes_preview = 8, .max_panel_summary = 80, .max_task = 16 });
|
|
defer std.testing.allocator.free(text);
|
|
try std.testing.expect(std.mem.indexOf(u8, text, "context:v1\n") != null);
|
|
try std.testing.expect(std.mem.indexOf(u8, text, "active_panel=diagnostics") != null);
|
|
try std.testing.expect(std.mem.indexOf(u8, text, "panel_summary=") != null);
|
|
try std.testing.expect(std.mem.indexOf(u8, text, "buffer_preview=pub") != null);
|
|
try std.testing.expect(std.mem.indexOf(u8, text, "task=fix_bug") != null);
|
|
}
|
|
|
|
test "regular: local context is line oriented and sanitized" {
|
|
const text = try localContextAlloc(std.testing.allocator, "/tmp/mim repo", "ship v1", null, .{});
|
|
defer std.testing.allocator.free(text);
|
|
try std.testing.expect(std.mem.indexOf(u8, text, "cwd=/tmp/mim_repo") != null);
|
|
try std.testing.expect(std.mem.indexOf(u8, text, "task=ship_v1") != null);
|
|
}
|
|
|
|
test "adversarial: context output is bounded" {
|
|
const long = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
|
|
const text = try localContextAlloc(std.testing.allocator, long, long, long, .{ .max_panel_summary = 8, .max_task = 8 });
|
|
defer std.testing.allocator.free(text);
|
|
try std.testing.expect(std.mem.indexOf(u8, text, "cwd=aaaaaaaa...") != null);
|
|
try std.testing.expect(std.mem.indexOf(u8, text, "task=aaaaaaaa...") != null);
|
|
}
|