diff options
Diffstat (limited to 'src/main.zig')
| -rw-r--r-- | src/main.zig | 102 |
1 files changed, 40 insertions, 62 deletions
diff --git a/src/main.zig b/src/main.zig index 46b6320..247817a 100644 --- a/src/main.zig +++ b/src/main.zig @@ -329,48 +329,51 @@ pub fn main(init: std.process.Init) !void { }; const banner_provider_initial: []const u8 = model_ref.provider; + // The session store: a directory-backed JSONL catalog rooted at the + // per-cwd `session_dir` (the CLI owns the cwd→dir grouping; the store + // is cwd-agnostic). Must outlive the agent, which appends through a + // `Session` handle minted/resolved below. + var session_store_impl = try panto.session_manager.FileSystemJSONLStore.init(alloc, io, session_dir, cwd); + defer session_store_impl.deinit(); + const session_store = session_store_impl.store(); + // Create or resume the session. Resume failures (missing/ambiguous id) // are user errors — print a tidy message and exit 1 rather than // printing a Zig stack trace. - var session_mgr = openSession( - alloc, - io, - session_dir, - cwd, + var session = openSession( + session_store, cli_flags, + session_dir, stdout, &stdout_file, ) catch |err| switch (err) { error.SessionNotFound, error.AmbiguousSessionId => std.process.exit(1), else => return err, }; - defer session_mgr.deinit(); + // `session.info` is adopted by the agent below (`Agent.init` can't fail) + // and freed in the agent's `deinit`; no separate cleanup here. - const is_resume = session_mgr.getEntries().len > 0; + const is_resume = cli_flags.resume_kind != .none and session.info.message_count > 0; - // On resume, reconstruct the conversation from the store. The dangling - // user prompt (a trailing user entry with no assistant reply) is split - // out and currently ignored (a future TUI will prefill it for editing). - // On a fresh session, the agent starts with an empty conversation. + // On resume, reconstruct the conversation from the store. (The + // dangling-prompt recovery feature was dropped in R2.) On a fresh + // session, the agent starts with an empty conversation. var adopted_conversation: ?panto.conversation.Conversation = null; if (is_resume) { - var loaded = try session_mgr.loadConversation(alloc); - // We don't use the dangling prompt yet; free it. - if (loaded.dangling_user) |d| alloc.free(d); - loaded.dangling_user = null; - adopted_conversation = loaded.conversation; + adopted_conversation = try session.load(); + const sid = session.info.id; try stdout.print( - "resumed session {s} ({d} entries)\n", - .{ session_mgr.getSessionId()[0..@min(8, session_mgr.getSessionId().len)], session_mgr.getEntries().len }, + "resumed session {s} ({d} messages)\n", + .{ sid[0..@min(8, sid.len)], session.info.message_count }, ); } // Assemble the active configuration snapshot and build the agent now, // before luarocks bootstrap and extension loading. The agent adopts the - // store (for free persistence) and the resumed conversation (or starts - // fresh). The agent owns its own tool registry (post-R1); extensions - // register their tool source onto it *after* the agent is built but - // before the first turn, so in-place registration is visible. + // session handle (for free persistence) and the resumed conversation + // (or starts fresh). The agent owns its own tool registry (post-R1); + // extensions register their tool source onto it *after* the agent is + // built but before the first turn, so in-place registration is visible. // System-prompt seeding/reconciliation below runs *through* the agent // so those entries persist. const active_config: panto.config.Config = .{ @@ -381,12 +384,10 @@ pub fn main(init: std.process.Init) !void { alloc, io, &active_config, - session_mgr.store(), + session, adopted_conversation, ); defer agent.deinit(); - agent.persist_provider = banner_provider_initial; - agent.persist_model = banner_model_initial; // Spin up the long-lived Lua runtime. All Lua extensions load into // one `lua_State`; module-global state survives across calls. The @@ -642,54 +643,31 @@ fn parseAgentFlags(alloc: std.mem.Allocator, args: std.process.Args) !AgentFlags // Session bootstrap // ----------------------------------------------------------------------------- +/// Mint or resolve the `Session` to drive, against the catalog `store`. +/// Fresh sessions are created on demand; resume resolves by id or picks the +/// most recent. The returned `Session` owns its `info` (freed via the +/// agent's `deinit`, which adopts it). fn openSession( - alloc: std.mem.Allocator, - io: std.Io, - session_dir: []const u8, - cwd: []const u8, + store: panto.session_store.SessionStore, flags: AgentFlags, + session_dir: []const u8, stdout: *std.Io.Writer, stdout_file: *std.Io.File.Writer, -) !panto.session_manager.SessionManager { +) !panto.session_store.Session { switch (flags.resume_kind) { - .none => return try panto.session_manager.SessionManager.init( - alloc, - io, - session_dir, - cwd, - ), + .none => return store.create(), .most_recent => { - const path_opt = panto.session_manager.findMostRecentSession(alloc, io, session_dir) catch null; - if (path_opt) |path| { - defer alloc.free(path); - return try panto.session_manager.SessionManager.open(alloc, io, path); - } + if (try store.latest()) |sess| return sess; try stdout.print("no sessions to resume; starting fresh.\n", .{}); try stdout_file.flush(); - return try panto.session_manager.SessionManager.init( - alloc, - io, - session_dir, - cwd, - ); + return store.create(); }, .by_id => { const id = flags.resume_id.?; - const path = panto.session_manager.resolveSessionId(alloc, io, session_dir, id) catch |err| switch (err) { - error.SessionNotFound => { - try stdout.print("error: no session matching '{s}' in {s}\n", .{ id, session_dir }); - try stdout_file.flush(); - return err; - }, - error.AmbiguousSessionId => { - try stdout.print("error: session id '{s}' is ambiguous\n", .{id}); - try stdout_file.flush(); - return err; - }, - else => return err, - }; - defer alloc.free(path); - return try panto.session_manager.SessionManager.open(alloc, io, path); + if (try store.resolve(id)) |sess| return sess; + try stdout.print("error: no session matching '{s}' in {s}\n", .{ id, session_dir }); + try stdout_file.flush(); + return error.SessionNotFound; }, } } |
