Add new features and update configurations for improved functionality

This commit is contained in:
ImBenji
2026-04-11 12:34:00 +01:00
parent fa4415553d
commit 0b6b604c56
125 changed files with 14119 additions and 1664 deletions
+15 -2
View File
@@ -21,10 +21,10 @@ class ConversationHistory {
_session = s;
}
void addMessage(String role, String content, {int? tokens}) {
void addMessage(String role, String content, {int? tokens, int? contextTokens}) {
if (_session == null) return;
final msg = Message(role: role, content: content, tokens: tokens);
final msg = Message(role: role, content: content, tokens: tokens, contextTokens: contextTokens);
_session!.messages.add(msg);
_session!.updated = DateTime.now().toUtc();
@@ -41,10 +41,23 @@ class ConversationHistory {
content: "${lastMessage.content}$text",
timestamp: lastMessage.timestamp,
tokens: lastMessage.tokens,
contextTokens: lastMessage.contextTokens,
);
_session!.updated = DateTime.now().toUtc();
}
void setLastMessageContextTokens(int contextTokens) {
if (_session == null || _session!.messages.isEmpty) return;
final last = _session!.messages.last;
_session!.messages[_session!.messages.length - 1] = Message(
role: last.role,
content: last.content,
timestamp: last.timestamp,
tokens: last.tokens,
contextTokens: contextTokens,
);
}
void removeLastMessage() {
if (_session == null || _session!.messages.isEmpty) {
return;
+40 -45
View File
@@ -1,18 +1,27 @@
// Parity exception: Claude Code stores sessions in ~/.claude/projects/<sanitized-cwd>/<id>.jsonl
// The Agency uses <workingDirectory>/.the_agency/sessions/<id>.json instead, because the UI
// is multi-project and it makes more sense for session data to live alongside the project.
import "dart:convert";
import "dart:io";
import "../local_state.dart";
import "package:path/path.dart" as p;
import "session_types.dart";
const _encoder = JsonEncoder.withIndent(" ");
// sessions live in ~/.clawd_code/sessions/{id}.json
String getSessionsDir() {
return joinPath(getConfigHomeDir(), "sessions");
// sessions live in <workingDirectory>/.the_agency/sessions/{id}.json
String getProjectAgencyDir(String workingDirectory) {
return p.join(workingDirectory, ".the_agency");
}
String _sessionPath(String id) {
return joinPath(getSessionsDir(), "$id.json");
String getProjectSessionsDir(String workingDirectory) {
return p.join(getProjectAgencyDir(workingDirectory), "sessions");
}
String _sessionPath(String workingDirectory, String id) {
return p.join(getProjectSessionsDir(workingDirectory), "$id.json");
}
class SessionStore {
@@ -20,20 +29,22 @@ class SessionStore {
static final SessionStore instance = SessionStore._();
Future<void> saveSession(ConversationSession session) async {
final dir = Directory(getSessionsDir());
final workingDir = session.workingDirectory;
if (workingDir == null || workingDir.isEmpty) return;
final dir = Directory(getProjectSessionsDir(workingDir));
if (!await dir.exists()) {
await dir.create(recursive: true);
}
final file = File(_sessionPath(session.id));
final file = File(_sessionPath(workingDir, session.id));
final json = _encoder.convert(session.toJson());
await file.writeAsString("$json\n");
}
Future<ConversationSession?> loadSession(String id) async {
final file = File(_sessionPath(id));
Future<ConversationSession?> loadSession(String id, {required String workingDirectory}) async {
final file = File(_sessionPath(workingDirectory, id));
if (!await file.exists()) return null;
try {
@@ -43,15 +54,15 @@ class SessionStore {
return ConversationSession.fromJson(decoded);
}
} catch (_) {
// corrupt file - just return null
// corrupt file return null
}
return null;
}
// returns summaries sorted newest first
Future<List<SessionSummary>> listSessions() async {
final dir = Directory(getSessionsDir());
// lists sessions for a single project, sorted newest first
Future<List<SessionSummary>> listSessionsForProject(String workingDirectory) async {
final dir = Directory(getProjectSessionsDir(workingDirectory));
if (!await dir.exists()) return <SessionSummary>[];
final summaries = <SessionSummary>[];
@@ -76,39 +87,23 @@ class SessionStore {
return summaries;
}
Future<bool> deleteSession(String id) async {
final file = File(_sessionPath(id));
// lists all sessions across multiple projects, sorted newest first
Future<List<SessionSummary>> listAllSessions(List<String> workingDirectories) async {
final all = <SessionSummary>[];
for (final dir in workingDirectories) {
all.addAll(await listSessionsForProject(dir));
}
all.sort((a, b) => b.updated.compareTo(a.updated));
return all;
}
Future<bool> deleteSession(String id, {required String workingDirectory}) async {
final file = File(_sessionPath(workingDirectory, id));
if (!await file.exists()) return false;
await file.delete();
return true;
}
// case insensitive search by name
Future<ConversationSession?> findSessionByName(String name) async {
final dir = Directory(getSessionsDir());
if (!await dir.exists()) return null;
final lowerName = name.toLowerCase();
await for (final entity in dir.list()) {
if (entity is! File) continue;
if (!entity.path.endsWith(".json")) continue;
try {
final raw = await entity.readAsString();
final decoded = jsonDecode(raw);
if (decoded is Map<String, dynamic>) {
final sess = ConversationSession.fromJson(decoded);
if (sess.name.toLowerCase() == lowerName) {
return sess;
}
}
} catch (_) {
continue;
}
}
return null;
}
}
+7
View File
@@ -7,6 +7,7 @@ class Message {
required this.content,
DateTime? timestamp,
this.tokens,
this.contextTokens,
}) : timestamp = timestamp ?? DateTime.now().toUtc();
factory Message.fromJson(Map<String, dynamic> json) {
@@ -17,6 +18,7 @@ class Message {
? DateTime.tryParse(json["timestamp"] as String)
: null,
tokens: json["tokens"] as int?,
contextTokens: json["contextTokens"] as int?,
);
}
@@ -27,12 +29,17 @@ class Message {
// approx token count - may be null if not tracked
final int? tokens;
// full context window size from the last API response usage field
// (input + cache_creation + cache_read + output), same as Claude Code
final int? contextTokens;
Map<String, dynamic> toJson() {
return <String, dynamic>{
"role": role,
"content": content,
"timestamp": timestamp.toIso8601String(),
if (tokens != null) "tokens": tokens,
if (contextTokens != null) "contextTokens": contextTokens,
};
}