Add command files and enhance session management features
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import "package:flutter/foundation.dart";
|
||||
import "package:flutter/scheduler.dart";
|
||||
import "dart:typed_data";
|
||||
|
||||
import "../../src/chat/tool_loop_service.dart";
|
||||
@@ -6,9 +7,12 @@ import "../../src/compact/compact_service.dart";
|
||||
import "../../src/hooks/hook_loader.dart";
|
||||
import "../../src/hooks/hook_runner.dart";
|
||||
import "../../src/permissions/permission_types.dart";
|
||||
import "../../src/services/cost_tracker.dart" as cost_tracker;
|
||||
import "../../src/session/session_runtime.dart";
|
||||
import "../../src/session/session_store.dart";
|
||||
import "../../src/session/session_types.dart";
|
||||
import "../models/attachment.dart";
|
||||
import "cost_provider.dart";
|
||||
import "settings_provider.dart";
|
||||
|
||||
|
||||
@@ -20,18 +24,35 @@ import "settings_provider.dart";
|
||||
// running and save themselves to disk; when you switch back you see their
|
||||
// live state.
|
||||
class ChatProvider extends ChangeNotifier {
|
||||
ChatProvider(this._settingsProvider) {
|
||||
ChatProvider(this._settingsProvider, this._costProvider) {
|
||||
_initHooks();
|
||||
}
|
||||
|
||||
final SettingsProvider _settingsProvider;
|
||||
final CostProvider _costProvider;
|
||||
|
||||
void Function(String sessionId, String newName)? onSessionNameChanged;
|
||||
|
||||
ToolLoopService _toolLoopService = ToolLoopService();
|
||||
HookRunner? _hookRunner;
|
||||
|
||||
final Map<String, SessionRuntime> _runtimes = {};
|
||||
final Map<String, ConversationSession> _sessions = {};
|
||||
String? _activeSessionId;
|
||||
|
||||
bool _notifyScheduled = false;
|
||||
|
||||
void _scheduleNotify() {
|
||||
if (_notifyScheduled) return;
|
||||
_notifyScheduled = true;
|
||||
|
||||
SchedulerBinding.instance.scheduleFrameCallback((_) {
|
||||
_notifyScheduled = false;
|
||||
notifyListeners();
|
||||
_costProvider.refreshCost();
|
||||
});
|
||||
}
|
||||
|
||||
// ─── hooks ──────────────────────────────────────────────────────────────────
|
||||
|
||||
Future<void> _initHooks() async {
|
||||
@@ -82,6 +103,10 @@ class ChatProvider extends ChangeNotifier {
|
||||
return r != null && r.hasUnreadResult;
|
||||
}
|
||||
|
||||
void markSessionRead(String sessionId) {
|
||||
_runtimes[sessionId]?.setUnreadResult(false);
|
||||
}
|
||||
|
||||
int get contextTokens {
|
||||
final msgs = messages;
|
||||
for (var i = msgs.length - 1; i >= 0; i--) {
|
||||
@@ -99,19 +124,46 @@ class ChatProvider extends ChangeNotifier {
|
||||
final id = session.id;
|
||||
|
||||
if (!_runtimes.containsKey(id)) {
|
||||
_sessions[id] = session;
|
||||
_runtimes[id] = SessionRuntime(
|
||||
session: session,
|
||||
toolLoopService: _toolLoopService,
|
||||
hookRunner: _hookRunner,
|
||||
getSettings: () => _settingsProvider.settings,
|
||||
normalizeModelId: (m) => _settingsProvider.normalizeModelId(m),
|
||||
onChanged: notifyListeners,
|
||||
onChanged: _scheduleNotify,
|
||||
onCostAdded: (_) {
|
||||
final s = _sessions[id];
|
||||
if (s != null) SessionStore.instance.saveSession(s);
|
||||
},
|
||||
isActive: () => _activeSessionId == id,
|
||||
onNameGenerated: (sid, name) {
|
||||
onSessionNameChanged?.call(sid, name);
|
||||
notifyListeners();
|
||||
},
|
||||
onPersistAllowRule: (rule) => _settingsProvider.addAlwaysAllowRule(rule),
|
||||
);
|
||||
}
|
||||
|
||||
_activeSessionId = id;
|
||||
_runtimes[id]?.markRead();
|
||||
|
||||
// sync global cost tracker to this thread's persisted cost
|
||||
cost_tracker.resetCostState();
|
||||
final sessionCost = (_sessions[id] ?? session).cost;
|
||||
if (sessionCost > 0) {
|
||||
cost_tracker.setCostStateForRestore(
|
||||
totalCostUsd: sessionCost,
|
||||
totalApiDurationMs: 0,
|
||||
totalApiDurationWithoutRetriesMs: 0,
|
||||
totalToolDurationMs: 0,
|
||||
totalLinesAdded: 0,
|
||||
totalLinesRemoved: 0,
|
||||
);
|
||||
}
|
||||
|
||||
notifyListeners();
|
||||
_costProvider.refreshCost();
|
||||
}
|
||||
|
||||
// Fast-path: switch focus to an already-running runtime without touching disk.
|
||||
@@ -164,8 +216,8 @@ class ChatProvider extends ChangeNotifier {
|
||||
_active?.runCompact(customInstructions: customInstructions) ??
|
||||
Future.value();
|
||||
|
||||
Future<void> resolvePermission(PermissionDecision decision) =>
|
||||
_active?.resolvePermission(decision) ?? Future.value();
|
||||
Future<void> resolvePermission(PermissionDecision decision, {String? persistRule}) =>
|
||||
_active?.resolvePermission(decision, persistRule: persistRule) ?? Future.value();
|
||||
|
||||
void removeQueuedMessage(int index) => _active?.removeQueuedMessage(index);
|
||||
|
||||
|
||||
@@ -12,8 +12,6 @@ class CostProvider extends ChangeNotifier {
|
||||
String getFormattedTotalCost() => cost_tracker.formatTotalCost();
|
||||
|
||||
Future<void> refreshCost() async {
|
||||
// read current values from cost tracker
|
||||
final _ = cost_tracker.getTotalCostUsd();
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,9 @@ import "settings_provider.dart";
|
||||
|
||||
class HomeCoordinator extends ChangeNotifier {
|
||||
|
||||
HomeCoordinator(this._projects, this._session, this._chat, this._settings);
|
||||
HomeCoordinator(this._projects, this._session, this._chat, this._settings) {
|
||||
_chat.onSessionNameChanged = (_, __) => _session.refreshSessions();
|
||||
}
|
||||
|
||||
final ProjectsProvider _projects;
|
||||
final SessionProvider _session;
|
||||
@@ -86,6 +88,7 @@ class HomeCoordinator extends ChangeNotifier {
|
||||
// without reloading from disk — avoids disrupting an in-progress turn.
|
||||
if (_chat.isSessionRunning(session.id)) {
|
||||
_chat.activateSessionById(session.id);
|
||||
_chat.markSessionRead(session.id);
|
||||
_session.setActiveSessionId(session.id);
|
||||
_projects.selectProjectByWorkingDirectory(session.workingDirectory);
|
||||
_settings.setThreadModel(session.model);
|
||||
@@ -96,6 +99,7 @@ class HomeCoordinator extends ChangeNotifier {
|
||||
final loaded = _session.currentSession;
|
||||
if (loaded != null) {
|
||||
_chat.activateSession(loaded);
|
||||
_chat.markSessionRead(session.id);
|
||||
}
|
||||
_projects.selectProjectByWorkingDirectory(_session.activeWorkingDirectory);
|
||||
_settings.setThreadModel(_session.currentSession?.model);
|
||||
@@ -120,6 +124,7 @@ class HomeCoordinator extends ChangeNotifier {
|
||||
final newSession = _session.currentSession;
|
||||
if (newSession != null) {
|
||||
_chat.activateSession(newSession);
|
||||
_chat.markSessionRead(newSession.id);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -82,6 +82,22 @@ class SettingsProvider extends ChangeNotifier {
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
Future<void> updateAdvisorEffortLevel(String? level) async {
|
||||
await _settingsStore.update(
|
||||
(current) => current.copyWith(advisorEffortLevel: level),
|
||||
);
|
||||
_globalSettings = _settingsStore.settings;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
Future<void> updateAdvisorModel(String? model) async {
|
||||
await _settingsStore.update(
|
||||
(current) => current.copyWith(advisorModel: model),
|
||||
);
|
||||
_globalSettings = _settingsStore.settings;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
Future<void> updateEffortLevel(String newLevel) async {
|
||||
await _settingsStore.update(
|
||||
(current) => current.copyWith(effortLevel: newLevel),
|
||||
|
||||
Reference in New Issue
Block a user