Remove runtime session task binding
This commit is contained in:
parent
71b78dae66
commit
87c978d456
@ -68,10 +68,6 @@ extension AppControllerDesktopNavigation on AppController {
|
||||
}
|
||||
|
||||
void navigateHome() {
|
||||
final mainSessionKey =
|
||||
runtimeInternal.snapshot.mainSessionKey?.trim().isNotEmpty == true
|
||||
? runtimeInternal.snapshot.mainSessionKey!.trim()
|
||||
: 'main';
|
||||
final homeDestination =
|
||||
capabilities.supportsDestination(WorkspaceDestination.assistant)
|
||||
? WorkspaceDestination.assistant
|
||||
@ -90,8 +86,8 @@ extension AppControllerDesktopNavigation on AppController {
|
||||
if (destinationChanged || detailChanged || settingsDrillInChanged) {
|
||||
notifyListeners();
|
||||
}
|
||||
if (sessionsControllerInternal.currentSessionKey != mainSessionKey) {
|
||||
unawaited(switchSession(mainSessionKey));
|
||||
if (!isAppOwnedAssistantSessionKeyInternal(currentSessionKey)) {
|
||||
unawaited(ensureActiveAssistantThreadInternal());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -187,7 +187,7 @@ String? assistantRemoteWorkingDirectoryHintForSessionRuntimeInternal(
|
||||
);
|
||||
final candidate =
|
||||
controller
|
||||
.assistantThreadRecordsInternal[normalizedSessionKey]
|
||||
.taskThreadForSessionInternal(normalizedSessionKey)
|
||||
?.lastRemoteWorkingDirectory
|
||||
?.trim() ??
|
||||
'';
|
||||
@ -202,9 +202,7 @@ String? resolveLocalAssistantWorkingDirectoryForSessionRuntimeInternal(
|
||||
String sessionKey, {
|
||||
bool requireLocalExistence = true,
|
||||
}) {
|
||||
final record =
|
||||
controller.assistantThreadRecordsInternal[controller
|
||||
.normalizedAssistantSessionKeyInternal(sessionKey)];
|
||||
final record = controller.taskThreadForSessionInternal(sessionKey);
|
||||
if (record?.workspaceKind != WorkspaceKind.localFs) {
|
||||
return null;
|
||||
}
|
||||
@ -328,7 +326,7 @@ void clearCodexGatewayRegistrationRuntimeInternal(AppController controller) {
|
||||
|
||||
void recomputeTasksRuntimeInternal(AppController controller) {
|
||||
controller.tasksControllerInternal.recompute(
|
||||
sessions: controller.sessions,
|
||||
sessions: controller.assistantSessions,
|
||||
cronJobs: controller.cronJobsControllerInternal.items,
|
||||
currentSessionKey: controller.sessionsControllerInternal.currentSessionKey,
|
||||
hasPendingRun: controller.hasAssistantPendingRun,
|
||||
|
||||
@ -307,22 +307,18 @@ extension AppControllerDesktopSettings on AppController {
|
||||
openClawGatewayQueuedTurnsBySessionInternal.clear();
|
||||
openClawGatewayActiveTasksInternal = 0;
|
||||
multiAgentRunPendingInternal = false;
|
||||
final sessionKey = createAssistantDraftSessionKeyInternal();
|
||||
initializeAssistantThreadContext(
|
||||
'main',
|
||||
sessionKey,
|
||||
executionTarget: sanitizePersistedExecutionTargetInternal(
|
||||
currentSettings.assistantExecutionTarget,
|
||||
),
|
||||
messageViewMode: AssistantMessageViewMode.rendered,
|
||||
);
|
||||
await setCurrentAssistantSessionKeyInternal(
|
||||
'main',
|
||||
sessionKey,
|
||||
persistSelection: false,
|
||||
);
|
||||
taskThreadRepositoryInternal.removeWhere(
|
||||
(key, _) => key != 'main',
|
||||
persist: false,
|
||||
);
|
||||
assistantThreadMessagesInternal.removeWhere((key, _) => key != 'main');
|
||||
await flushAssistantThreadPersistenceInternal();
|
||||
await storeInternal.saveTaskThreads(
|
||||
taskThreadRepositoryInternal.snapshot(),
|
||||
|
||||
@ -540,7 +540,6 @@ extension AppControllerDesktopSettingsRuntime on AppController {
|
||||
'',
|
||||
);
|
||||
sessionsControllerInternal.configure(
|
||||
mainSessionKey: runtimeInternal.snapshot.mainSessionKey ?? 'main',
|
||||
selectedAgentId: agentsControllerInternal.selectedAgentId,
|
||||
defaultAgentId: '',
|
||||
);
|
||||
|
||||
@ -247,6 +247,11 @@ extension AppControllerDesktopSkillPermissions on AppController {
|
||||
final normalizedSessionKey = normalizedAssistantSessionKeyInternal(
|
||||
sessionKey,
|
||||
);
|
||||
if (!isAppOwnedAssistantSessionKeyInternal(normalizedSessionKey)) {
|
||||
throw StateError(
|
||||
'Runtime session key "$normalizedSessionKey" cannot be used as an app task.',
|
||||
);
|
||||
}
|
||||
final existing = taskThreadForSessionInternal(normalizedSessionKey);
|
||||
final nextExecutionTarget =
|
||||
executionTarget ??
|
||||
@ -439,11 +444,17 @@ extension AppControllerDesktopSkillPermissions on AppController {
|
||||
String sessionKey, {
|
||||
bool persistSelection = true,
|
||||
}) async {
|
||||
final normalizedSessionKey = normalizedAssistantSessionKeyInternal(
|
||||
var normalizedSessionKey = normalizedAssistantSessionKeyInternal(
|
||||
sessionKey,
|
||||
);
|
||||
if (normalizedSessionKey.isEmpty) {
|
||||
return;
|
||||
if (!isAppOwnedAssistantSessionKeyInternal(normalizedSessionKey)) {
|
||||
normalizedSessionKey = createAssistantDraftSessionKeyInternal();
|
||||
initializeAssistantThreadContext(
|
||||
normalizedSessionKey,
|
||||
title: appText('新对话', 'New conversation'),
|
||||
executionTarget: currentAssistantExecutionTarget,
|
||||
messageViewMode: currentAssistantMessageViewMode,
|
||||
);
|
||||
}
|
||||
await sessionsControllerInternal.switchSession(normalizedSessionKey);
|
||||
if (persistSelection) {
|
||||
|
||||
@ -144,7 +144,6 @@ extension AppControllerDesktopThreadActions on AppController {
|
||||
Future<void> refreshAgents() async {
|
||||
await agentsControllerInternal.refresh();
|
||||
sessionsControllerInternal.configure(
|
||||
mainSessionKey: runtimeInternal.snapshot.mainSessionKey ?? 'main',
|
||||
selectedAgentId: agentsControllerInternal.selectedAgentId,
|
||||
defaultAgentId: '',
|
||||
);
|
||||
@ -165,13 +164,13 @@ extension AppControllerDesktopThreadActions on AppController {
|
||||
refreshAfterSave: false,
|
||||
);
|
||||
sessionsControllerInternal.configure(
|
||||
mainSessionKey: runtimeInternal.snapshot.mainSessionKey ?? 'main',
|
||||
selectedAgentId: agentsControllerInternal.selectedAgentId,
|
||||
defaultAgentId: '',
|
||||
);
|
||||
await chatControllerInternal.loadSession(
|
||||
sessionsControllerInternal.currentSessionKey,
|
||||
);
|
||||
final sessionKey = normalizedAssistantSessionKeyInternal(currentSessionKey);
|
||||
if (isAppOwnedAssistantSessionKeyInternal(sessionKey)) {
|
||||
await chatControllerInternal.loadSession(sessionKey);
|
||||
}
|
||||
await skillsControllerInternal.refresh(
|
||||
agentId: agentsControllerInternal.selectedAgentId.isEmpty
|
||||
? null
|
||||
@ -181,33 +180,26 @@ extension AppControllerDesktopThreadActions on AppController {
|
||||
}
|
||||
|
||||
Future<void> refreshSessions() async {
|
||||
final selectedSessionKey = normalizedAssistantSessionKeyInternal(
|
||||
sessionsControllerInternal.currentSessionKey,
|
||||
);
|
||||
final preserveSelectedLocalTask =
|
||||
!isAssistantTaskArchived(selectedSessionKey) &&
|
||||
hasAssistantTaskStateInternal(selectedSessionKey);
|
||||
sessionsControllerInternal.configure(
|
||||
mainSessionKey: runtimeInternal.snapshot.mainSessionKey ?? 'main',
|
||||
selectedAgentId: agentsControllerInternal.selectedAgentId,
|
||||
defaultAgentId: '',
|
||||
);
|
||||
await sessionsControllerInternal.refresh();
|
||||
if (preserveSelectedLocalTask &&
|
||||
!matchesSessionKey(
|
||||
selectedSessionKey,
|
||||
sessionsControllerInternal.currentSessionKey,
|
||||
)) {
|
||||
await sessionsControllerInternal.switchSession(selectedSessionKey);
|
||||
}
|
||||
await chatControllerInternal.loadSession(
|
||||
await ensureActiveAssistantThreadInternal();
|
||||
final selectedSessionKey = normalizedAssistantSessionKeyInternal(
|
||||
sessionsControllerInternal.currentSessionKey,
|
||||
);
|
||||
if (isAppOwnedAssistantSessionKeyInternal(selectedSessionKey)) {
|
||||
await chatControllerInternal.loadSession(selectedSessionKey);
|
||||
}
|
||||
recomputeTasksInternal();
|
||||
}
|
||||
|
||||
Future<void> switchSession(String sessionKey) async {
|
||||
final nextSessionKey = normalizedAssistantSessionKeyInternal(sessionKey);
|
||||
var nextSessionKey = normalizedAssistantSessionKeyInternal(sessionKey);
|
||||
if (!isAppOwnedAssistantSessionKeyInternal(nextSessionKey)) {
|
||||
nextSessionKey = createAssistantDraftSessionKeyInternal();
|
||||
}
|
||||
final nextTarget = assistantExecutionTargetForSession(nextSessionKey);
|
||||
final nextViewMode = assistantMessageViewModeForSession(nextSessionKey);
|
||||
|
||||
@ -245,9 +237,15 @@ extension AppControllerDesktopThreadActions on AppController {
|
||||
const <CollaborationAttachment>[],
|
||||
List<String> selectedSkillLabels = const <String>[],
|
||||
}) async {
|
||||
final sessionKey = normalizedAssistantSessionKeyInternal(
|
||||
var sessionKey = normalizedAssistantSessionKeyInternal(
|
||||
sessionsControllerInternal.currentSessionKey,
|
||||
);
|
||||
if (!isAppOwnedAssistantSessionKeyInternal(sessionKey)) {
|
||||
await ensureActiveAssistantThreadInternal();
|
||||
sessionKey = normalizedAssistantSessionKeyInternal(
|
||||
sessionsControllerInternal.currentSessionKey,
|
||||
);
|
||||
}
|
||||
final currentTarget = assistantExecutionTargetForSession(sessionKey);
|
||||
final resumeSessionHint = shouldResumeGatewaySessionForNextSendInternal(
|
||||
sessionKey,
|
||||
|
||||
@ -95,6 +95,9 @@ extension AppControllerDesktopThreadBinding on AppController {
|
||||
final normalizedSessionKey = normalizedAssistantSessionKeyInternal(
|
||||
sessionKey,
|
||||
);
|
||||
if (!isAppOwnedAssistantSessionKeyInternal(normalizedSessionKey)) {
|
||||
return '';
|
||||
}
|
||||
final homeDirectory = resolvedUserHomeDirectoryInternal.trim();
|
||||
if (homeDirectory.isEmpty) {
|
||||
return '';
|
||||
@ -114,6 +117,9 @@ extension AppControllerDesktopThreadBinding on AppController {
|
||||
final normalizedSessionKey = normalizedAssistantSessionKeyInternal(
|
||||
sessionKey,
|
||||
);
|
||||
if (!isAppOwnedAssistantSessionKeyInternal(normalizedSessionKey)) {
|
||||
return '';
|
||||
}
|
||||
return '\$HOME/.xworkmate/threads/${threadWorkspaceDirectoryNameInternal(normalizedSessionKey)}';
|
||||
}
|
||||
|
||||
|
||||
@ -48,6 +48,11 @@ import 'app_controller_desktop_thread_sessions_collaboration_impl.dart';
|
||||
|
||||
// ignore_for_file: invalid_use_of_visible_for_testing_member, invalid_use_of_protected_member
|
||||
|
||||
final RegExp _runtimeSessionKeyPatternInternal = RegExp(
|
||||
r'^session-\d+$',
|
||||
caseSensitive: false,
|
||||
);
|
||||
|
||||
AssistantThreadConnectionState resolveGatewayThreadConnectionStateInternal({
|
||||
required AssistantExecutionTarget target,
|
||||
required bool bridgeReady,
|
||||
@ -186,6 +191,26 @@ bool bridgeCapabilityReadyForExecutionTargetInternal({
|
||||
}
|
||||
|
||||
extension AppControllerDesktopThreadSessions on AppController {
|
||||
bool isRuntimeOwnedAssistantSessionKeyInternal(String sessionKey) {
|
||||
final normalizedSessionKey = normalizedAssistantSessionKeyInternal(
|
||||
sessionKey,
|
||||
).toLowerCase();
|
||||
if (normalizedSessionKey.isEmpty) {
|
||||
return true;
|
||||
}
|
||||
if (normalizedSessionKey == 'main') {
|
||||
return true;
|
||||
}
|
||||
if (normalizedSessionKey.startsWith('agent:')) {
|
||||
return true;
|
||||
}
|
||||
return _runtimeSessionKeyPatternInternal.hasMatch(normalizedSessionKey);
|
||||
}
|
||||
|
||||
bool isAppOwnedAssistantSessionKeyInternal(String sessionKey) {
|
||||
return !isRuntimeOwnedAssistantSessionKeyInternal(sessionKey);
|
||||
}
|
||||
|
||||
AssistantExecutionTarget resolveAssistantExecutionTargetFromRecordsInternal(
|
||||
TaskThread? record,
|
||||
) {
|
||||
@ -206,6 +231,9 @@ extension AppControllerDesktopThreadSessions on AppController {
|
||||
final normalizedSessionKey = normalizedAssistantSessionKeyInternal(
|
||||
sessionKey,
|
||||
);
|
||||
if (!isAppOwnedAssistantSessionKeyInternal(normalizedSessionKey)) {
|
||||
return null;
|
||||
}
|
||||
return taskThreadRepositoryInternal.taskThreadForSession(
|
||||
normalizedSessionKey,
|
||||
);
|
||||
@ -224,6 +252,9 @@ extension AppControllerDesktopThreadSessions on AppController {
|
||||
final normalizedSessionKey = normalizedAssistantSessionKeyInternal(
|
||||
sessionKey,
|
||||
);
|
||||
if (!isAppOwnedAssistantSessionKeyInternal(normalizedSessionKey)) {
|
||||
return false;
|
||||
}
|
||||
return taskThreadRepositoryInternal.containsKey(normalizedSessionKey) ||
|
||||
assistantThreadMessagesInternal.containsKey(normalizedSessionKey) ||
|
||||
localSessionMessagesInternal.containsKey(normalizedSessionKey);
|
||||
@ -587,21 +618,12 @@ extension AppControllerDesktopThreadSessions on AppController {
|
||||
List<GatewaySessionSummary> assistantSessionsInternal() {
|
||||
final byKey = <String, GatewaySessionSummary>{};
|
||||
|
||||
for (final session in sessionsControllerInternal.sessions) {
|
||||
final normalizedSessionKey = normalizedAssistantSessionKeyInternal(
|
||||
session.key,
|
||||
);
|
||||
if (isAssistantTaskArchived(normalizedSessionKey)) {
|
||||
continue;
|
||||
}
|
||||
byKey[normalizedSessionKey] = session;
|
||||
}
|
||||
|
||||
for (final record in assistantThreadRecordsInternal.values) {
|
||||
final normalizedSessionKey = normalizedAssistantSessionKeyInternal(
|
||||
record.sessionKey,
|
||||
);
|
||||
if (normalizedSessionKey.isEmpty ||
|
||||
!isAppOwnedAssistantSessionKeyInternal(normalizedSessionKey) ||
|
||||
isAssistantTaskArchived(normalizedSessionKey) ||
|
||||
record.archived) {
|
||||
continue;
|
||||
@ -616,7 +638,8 @@ extension AppControllerDesktopThreadSessions on AppController {
|
||||
}
|
||||
|
||||
final currentKey = normalizedAssistantSessionKeyInternal(currentSessionKey);
|
||||
if (!isAssistantTaskArchived(currentKey) &&
|
||||
if (isAppOwnedAssistantSessionKeyInternal(currentKey) &&
|
||||
!isAssistantTaskArchived(currentKey) &&
|
||||
!byKey.containsKey(currentKey)) {
|
||||
byKey[currentKey] = assistantSessionSummaryForInternal(currentKey);
|
||||
}
|
||||
|
||||
@ -102,9 +102,12 @@ Future<void> runMultiAgentCollaborationThreadSessionInternal(
|
||||
required List<CollaborationAttachment> attachments,
|
||||
required List<String> selectedSkillLabels,
|
||||
}) async {
|
||||
final sessionKey = controller.currentSessionKey.trim().isEmpty
|
||||
? 'main'
|
||||
: controller.currentSessionKey;
|
||||
if (!controller.isAppOwnedAssistantSessionKeyInternal(
|
||||
controller.currentSessionKey,
|
||||
)) {
|
||||
await controller.ensureActiveAssistantThreadInternal();
|
||||
}
|
||||
final sessionKey = controller.currentSessionKey.trim();
|
||||
await controller.enqueueThreadTurnInternal<void>(sessionKey, () async {
|
||||
await controller.ensureDesktopTaskThreadBindingInternal(
|
||||
sessionKey,
|
||||
@ -383,11 +386,6 @@ bool canQuickConnectGatewayThreadSessionInternal(AppController controller) {
|
||||
profile.mode != defaults.mode;
|
||||
}
|
||||
|
||||
String normalizeAssistantSessionKeyThreadInternal(String sessionKey) {
|
||||
final trimmed = sessionKey.trim();
|
||||
return trimmed.isEmpty ? 'main' : trimmed;
|
||||
}
|
||||
|
||||
String joinConnectionPartsThreadSessionInternal(List<String> parts) {
|
||||
final normalized = parts
|
||||
.map((item) => item.trim())
|
||||
|
||||
@ -62,15 +62,19 @@ extension AppControllerDesktopThreadStorage on AppController {
|
||||
}
|
||||
|
||||
Future<void> ensureActiveAssistantThreadInternal() async {
|
||||
if (!isAssistantTaskArchived(
|
||||
final currentKey = normalizedAssistantSessionKeyInternal(
|
||||
sessionsControllerInternal.currentSessionKey,
|
||||
)) {
|
||||
);
|
||||
if (isAppOwnedAssistantSessionKeyInternal(currentKey) &&
|
||||
!isAssistantTaskArchived(currentKey)) {
|
||||
return;
|
||||
}
|
||||
final fallback = assistantSessionSummariesInternal().firstWhere(
|
||||
(item) => !isAssistantTaskArchived(item.key),
|
||||
(item) =>
|
||||
isAppOwnedAssistantSessionKeyInternal(item.key) &&
|
||||
!isAssistantTaskArchived(item.key),
|
||||
orElse: () => GatewaySessionSummary(
|
||||
key: 'draft:${DateTime.now().millisecondsSinceEpoch}',
|
||||
key: createAssistantDraftSessionKeyInternal(),
|
||||
kind: 'assistant',
|
||||
displayName: appText('新对话', 'New conversation'),
|
||||
surface: 'Assistant',
|
||||
@ -92,6 +96,14 @@ extension AppControllerDesktopThreadStorage on AppController {
|
||||
lastMessagePreview: null,
|
||||
),
|
||||
);
|
||||
if (!hasAssistantTaskStateInternal(fallback.key)) {
|
||||
initializeAssistantThreadContext(
|
||||
fallback.key,
|
||||
title: appText('新对话', 'New conversation'),
|
||||
executionTarget: currentAssistantExecutionTarget,
|
||||
messageViewMode: currentAssistantMessageViewMode,
|
||||
);
|
||||
}
|
||||
await setCurrentAssistantSessionKeyInternal(fallback.key);
|
||||
}
|
||||
|
||||
@ -100,9 +112,9 @@ extension AppControllerDesktopThreadStorage on AppController {
|
||||
appUiState.assistantLastSessionKey,
|
||||
);
|
||||
final known =
|
||||
normalized == 'main' ||
|
||||
assistantThreadRecordsInternal.containsKey(normalized) ||
|
||||
assistantThreadMessagesInternal.containsKey(normalized);
|
||||
isAppOwnedAssistantSessionKeyInternal(normalized) &&
|
||||
(assistantThreadRecordsInternal.containsKey(normalized) ||
|
||||
assistantThreadMessagesInternal.containsKey(normalized));
|
||||
if (normalized.isEmpty || !known || isAssistantTaskArchived(normalized)) {
|
||||
return;
|
||||
}
|
||||
@ -325,7 +337,8 @@ extension AppControllerDesktopThreadStorage on AppController {
|
||||
final sessionKey = normalizedAssistantSessionKeyInternal(
|
||||
record.sessionKey,
|
||||
);
|
||||
if (record.archived) {
|
||||
if (!isAppOwnedAssistantSessionKeyInternal(sessionKey) ||
|
||||
record.archived) {
|
||||
continue;
|
||||
}
|
||||
items.add(assistantSessionSummaryForInternal(sessionKey, record: record));
|
||||
@ -337,7 +350,9 @@ extension AppControllerDesktopThreadStorage on AppController {
|
||||
final hasCurrent = items.any(
|
||||
(item) => matchesSessionKey(item.key, currentSessionKey),
|
||||
);
|
||||
if (!hasCurrent && !isAssistantTaskArchived(currentSessionKey)) {
|
||||
if (isAppOwnedAssistantSessionKeyInternal(currentSessionKey) &&
|
||||
!hasCurrent &&
|
||||
!isAssistantTaskArchived(currentSessionKey)) {
|
||||
items.add(assistantSessionSummaryForInternal(currentSessionKey));
|
||||
}
|
||||
|
||||
|
||||
@ -51,14 +51,10 @@ class _AppShellState extends State<AppShell> {
|
||||
}
|
||||
|
||||
List<SidebarTaskItem> _buildSidebarTaskItems(AppController controller) {
|
||||
final currentSessionKey = controller.currentSessionKey.trim().isEmpty
|
||||
? 'main'
|
||||
: controller.currentSessionKey.trim();
|
||||
final currentSessionKey = controller.currentSessionKey.trim();
|
||||
return controller.assistantSessions
|
||||
.map((session) {
|
||||
final sessionKey = session.key.trim().isEmpty
|
||||
? 'main'
|
||||
: session.key.trim();
|
||||
final sessionKey = session.key.trim();
|
||||
final preview = session.lastMessagePreview?.trim() ?? '';
|
||||
return SidebarTaskItem(
|
||||
sessionKey: sessionKey,
|
||||
|
||||
@ -147,20 +147,6 @@ class DerivedTasksController extends ChangeNotifier {
|
||||
}
|
||||
}
|
||||
|
||||
String normalizeMainSessionKey(String? value) {
|
||||
final trimmed = value?.trim() ?? '';
|
||||
return trimmed.isEmpty ? 'main' : trimmed;
|
||||
}
|
||||
|
||||
String makeAgentSessionKey({required String agentId, required String baseKey}) {
|
||||
final trimmedAgent = agentId.trim();
|
||||
final trimmedBase = baseKey.trim();
|
||||
if (trimmedAgent.isEmpty) {
|
||||
return normalizeMainSessionKey(trimmedBase);
|
||||
}
|
||||
return 'agent:$trimmedAgent:${normalizeMainSessionKey(trimmedBase)}';
|
||||
}
|
||||
|
||||
bool matchesSessionKey(String incoming, String current) {
|
||||
final left = incoming.trim().toLowerCase();
|
||||
final right = current.trim().toLowerCase();
|
||||
|
||||
@ -93,8 +93,7 @@ class GatewaySessionsController extends ChangeNotifier {
|
||||
|
||||
List<GatewaySessionSummary> sessionsInternal =
|
||||
const <GatewaySessionSummary>[];
|
||||
String currentSessionKeyInternal = 'main';
|
||||
String mainSessionBaseKeyInternal = 'main';
|
||||
String currentSessionKeyInternal = '';
|
||||
String selectedAgentIdInternal = '';
|
||||
String defaultAgentIdInternal = '';
|
||||
bool loadingInternal = false;
|
||||
@ -104,37 +103,16 @@ class GatewaySessionsController extends ChangeNotifier {
|
||||
String get currentSessionKey => currentSessionKeyInternal;
|
||||
bool get loading => loadingInternal;
|
||||
String? get error => errorInternal;
|
||||
String get mainSessionBaseKey => mainSessionBaseKeyInternal;
|
||||
|
||||
void configure({
|
||||
required String mainSessionKey,
|
||||
required String selectedAgentId,
|
||||
required String defaultAgentId,
|
||||
}) {
|
||||
mainSessionBaseKeyInternal = normalizeMainSessionKey(mainSessionKey);
|
||||
selectedAgentIdInternal = selectedAgentId.trim();
|
||||
defaultAgentIdInternal = defaultAgentId.trim();
|
||||
final preferred = preferredSessionKey;
|
||||
if (currentSessionKeyInternal.trim().isEmpty ||
|
||||
currentSessionKeyInternal == 'main' ||
|
||||
currentSessionKeyInternal == mainSessionBaseKeyInternal ||
|
||||
currentSessionKeyInternal.startsWith('agent:')) {
|
||||
currentSessionKeyInternal = preferred;
|
||||
}
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
String get preferredSessionKey {
|
||||
final selected = selectedAgentIdInternal.trim();
|
||||
final defaultAgent = defaultAgentIdInternal.trim();
|
||||
final base = normalizeMainSessionKey(mainSessionBaseKeyInternal);
|
||||
if (selected.isEmpty ||
|
||||
(defaultAgent.isNotEmpty && selected == defaultAgent)) {
|
||||
return base;
|
||||
}
|
||||
return makeAgentSessionKey(agentId: selected, baseKey: base);
|
||||
}
|
||||
|
||||
Future<void> refresh() async {
|
||||
if (!runtimeInternal.isConnected) {
|
||||
sessionsInternal = const <GatewaySessionSummary>[];
|
||||
@ -147,11 +125,6 @@ class GatewaySessionsController extends ChangeNotifier {
|
||||
notifyListeners();
|
||||
try {
|
||||
sessionsInternal = await runtimeInternal.listSessions(limit: 50);
|
||||
if (!sessionsInternal.any(
|
||||
(item) => matchesSessionKey(item.key, currentSessionKeyInternal),
|
||||
)) {
|
||||
currentSessionKeyInternal = preferredSessionKey;
|
||||
}
|
||||
} catch (error) {
|
||||
errorInternal = error.toString();
|
||||
} finally {
|
||||
|
||||
@ -216,7 +216,7 @@ Widget _buildTestApp({
|
||||
width: 460,
|
||||
height: 640,
|
||||
child: AssistantArtifactSidebar(
|
||||
sessionKey: 'session-1',
|
||||
sessionKey: 'draft:test-task-a',
|
||||
threadTitle: 'Thread',
|
||||
workspacePath: '/tmp/thread',
|
||||
workspaceKind: WorkspaceRefKind.localPath,
|
||||
|
||||
@ -13,10 +13,12 @@ void main() {
|
||||
testWidgets(
|
||||
'does not fabricate providers when live capabilities are unavailable',
|
||||
(tester) async {
|
||||
final controller = AppController(environmentOverride: const <String, String>{});
|
||||
final controller = AppController(
|
||||
environmentOverride: const <String, String>{},
|
||||
);
|
||||
addTearDown(controller.dispose);
|
||||
|
||||
await controller.sessionsController.switchSession('session-1');
|
||||
await controller.sessionsController.switchSession('draft:test-task-a');
|
||||
|
||||
await tester.pumpWidget(
|
||||
_buildTestApp(child: _buildLowerPane(controller: controller)),
|
||||
@ -29,9 +31,10 @@ void main() {
|
||||
);
|
||||
expect(find.text('未提供'), findsNothing);
|
||||
|
||||
final providerButton = tester.widget<PopupMenuButton<SingleAgentProvider>>(
|
||||
find.byKey(const Key('assistant-provider-button')),
|
||||
);
|
||||
final providerButton = tester
|
||||
.widget<PopupMenuButton<SingleAgentProvider>>(
|
||||
find.byKey(const Key('assistant-provider-button')),
|
||||
);
|
||||
expect(providerButton.enabled, isFalse);
|
||||
|
||||
expect(
|
||||
@ -55,7 +58,7 @@ void main() {
|
||||
|
||||
testWidgets('shows mode-specific provider catalogs', (tester) async {
|
||||
final controller = AppController(
|
||||
environmentOverride: const <String, String>{},
|
||||
environmentOverride: const <String, String>{},
|
||||
initialBridgeProviderCatalog: const <SingleAgentProvider>[
|
||||
SingleAgentProvider.codex,
|
||||
SingleAgentProvider.opencode,
|
||||
@ -76,7 +79,7 @@ void main() {
|
||||
);
|
||||
addTearDown(controller.dispose);
|
||||
|
||||
await controller.sessionsController.switchSession('session-1');
|
||||
await controller.sessionsController.switchSession('draft:test-task-a');
|
||||
|
||||
await tester.pumpWidget(
|
||||
_buildTestApp(child: _buildLowerPane(controller: controller)),
|
||||
@ -110,7 +113,7 @@ void main() {
|
||||
);
|
||||
|
||||
final gatewayThread = controller
|
||||
.requireTaskThreadForSessionInternal('session-1')
|
||||
.requireTaskThreadForSessionInternal('draft:test-task-a')
|
||||
.copyWith(
|
||||
executionBinding: ExecutionBinding(
|
||||
executionMode: threadExecutionModeFromAssistantExecutionTarget(
|
||||
@ -151,7 +154,7 @@ void main() {
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
final agentThread = controller
|
||||
.requireTaskThreadForSessionInternal('session-1')
|
||||
.requireTaskThreadForSessionInternal('draft:test-task-a')
|
||||
.copyWith(
|
||||
executionBinding: ExecutionBinding(
|
||||
executionMode: threadExecutionModeFromAssistantExecutionTarget(
|
||||
@ -204,7 +207,7 @@ void main() {
|
||||
tester,
|
||||
) async {
|
||||
final controller = AppController(
|
||||
environmentOverride: const <String, String>{},
|
||||
environmentOverride: const <String, String>{},
|
||||
initialBridgeProviderCatalog: const <SingleAgentProvider>[
|
||||
SingleAgentProvider.codex,
|
||||
SingleAgentProvider.opencode,
|
||||
@ -213,7 +216,7 @@ void main() {
|
||||
);
|
||||
addTearDown(controller.dispose);
|
||||
|
||||
await controller.sessionsController.switchSession('session-1');
|
||||
await controller.sessionsController.switchSession('draft:test-task-a');
|
||||
|
||||
await tester.pumpWidget(
|
||||
_buildTestApp(child: _buildLowerPane(controller: controller)),
|
||||
@ -253,7 +256,7 @@ void main() {
|
||||
tester,
|
||||
) async {
|
||||
final controller = AppController(
|
||||
environmentOverride: const <String, String>{},
|
||||
environmentOverride: const <String, String>{},
|
||||
initialBridgeProviderCatalog: const <SingleAgentProvider>[
|
||||
SingleAgentProvider.codex,
|
||||
SingleAgentProvider.opencode,
|
||||
@ -274,12 +277,12 @@ void main() {
|
||||
);
|
||||
addTearDown(controller.dispose);
|
||||
|
||||
await controller.sessionsController.switchSession('session-1');
|
||||
await controller.sessionsController.switchSession('draft:test-task-a');
|
||||
controller.initializeAssistantThreadContext(
|
||||
'session-1',
|
||||
'draft:test-task-a',
|
||||
executionTarget: AssistantExecutionTarget.gateway,
|
||||
messageViewMode: controller.assistantMessageViewModeForSession(
|
||||
'session-1',
|
||||
'draft:test-task-a',
|
||||
),
|
||||
);
|
||||
controller.notifyListeners();
|
||||
@ -315,10 +318,12 @@ void main() {
|
||||
});
|
||||
|
||||
testWidgets('uses submit button instead of connect action', (tester) async {
|
||||
final controller = AppController(environmentOverride: const <String, String>{});
|
||||
final controller = AppController(
|
||||
environmentOverride: const <String, String>{},
|
||||
);
|
||||
addTearDown(controller.dispose);
|
||||
|
||||
await controller.sessionsController.switchSession('session-1');
|
||||
await controller.sessionsController.switchSession('draft:test-task-a');
|
||||
|
||||
var sendCount = 0;
|
||||
|
||||
|
||||
@ -186,9 +186,9 @@ void main() {
|
||||
});
|
||||
|
||||
controller.upsertTaskThreadInternal(
|
||||
'session-1',
|
||||
'draft:test-task-a',
|
||||
workspaceBinding: WorkspaceBinding(
|
||||
workspaceId: 'session-1',
|
||||
workspaceId: 'draft:test-task-a',
|
||||
workspaceKind: WorkspaceKind.localFs,
|
||||
workspacePath: localWorkspace.path,
|
||||
displayPath: localWorkspace.path,
|
||||
@ -201,27 +201,61 @@ void main() {
|
||||
expect(
|
||||
assistantWorkingDirectoryForSessionRuntimeInternal(
|
||||
controller,
|
||||
'session-1',
|
||||
'draft:test-task-a',
|
||||
),
|
||||
localWorkspace.path,
|
||||
);
|
||||
expect(
|
||||
resolveLocalAssistantWorkingDirectoryForSessionRuntimeInternal(
|
||||
controller,
|
||||
'session-1',
|
||||
'draft:test-task-a',
|
||||
),
|
||||
localWorkspace.path,
|
||||
);
|
||||
expect(
|
||||
assistantRemoteWorkingDirectoryHintForSessionRuntimeInternal(
|
||||
controller,
|
||||
'session-1',
|
||||
'draft:test-task-a',
|
||||
),
|
||||
remoteWorkspace.path,
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
test('runtime session keys do not resolve to app task workspaces', () async {
|
||||
final controller = AppController(
|
||||
environmentOverride: const <String, String>{},
|
||||
);
|
||||
addTearDown(controller.dispose);
|
||||
|
||||
final home = await Directory.systemTemp.createTemp(
|
||||
'xworkmate-runtime-key-workspace-',
|
||||
);
|
||||
addTearDown(() async {
|
||||
if (await home.exists()) {
|
||||
await home.delete(recursive: true);
|
||||
}
|
||||
});
|
||||
controller.resolvedUserHomeDirectoryInternal = home.path;
|
||||
|
||||
controller.initializeAssistantThreadContext(
|
||||
'draft:test-workspace-task',
|
||||
executionTarget: AssistantExecutionTarget.gateway,
|
||||
messageViewMode: AssistantMessageViewMode.rendered,
|
||||
);
|
||||
|
||||
expect(controller.localThreadWorkspacePathInternal('session-1'), isEmpty);
|
||||
expect(
|
||||
controller.localThreadWorkspaceDisplayPathInternal('session-1'),
|
||||
isEmpty,
|
||||
);
|
||||
expect(controller.assistantWorkspacePathForSession('session-1'), isEmpty);
|
||||
expect(
|
||||
controller.assistantWorkspacePathForSession('draft:test-workspace-task'),
|
||||
endsWith('/.xworkmate/threads/draft-test-workspace-task'),
|
||||
);
|
||||
});
|
||||
|
||||
test('writes inline ACP artifacts into the local thread workspace', () async {
|
||||
final controller = AppController(
|
||||
environmentOverride: const <String, String>{},
|
||||
@ -238,9 +272,9 @@ void main() {
|
||||
});
|
||||
|
||||
controller.upsertTaskThreadInternal(
|
||||
'session-1',
|
||||
'draft:test-task-a',
|
||||
workspaceBinding: WorkspaceBinding(
|
||||
workspaceId: 'session-1',
|
||||
workspaceId: 'draft:test-task-a',
|
||||
workspaceKind: WorkspaceKind.localFs,
|
||||
workspacePath: localWorkspace.path,
|
||||
displayPath: localWorkspace.path,
|
||||
@ -267,20 +301,20 @@ void main() {
|
||||
);
|
||||
|
||||
await controller.persistGoTaskArtifactsForSessionInternal(
|
||||
'session-1',
|
||||
'draft:test-task-a',
|
||||
result,
|
||||
);
|
||||
|
||||
final artifact = File('${localWorkspace.path}/notes/hello.txt');
|
||||
expect(await artifact.readAsString(), 'artifact body');
|
||||
await controller.persistGoTaskArtifactsForSessionInternal(
|
||||
'session-1',
|
||||
'draft:test-task-a',
|
||||
result,
|
||||
);
|
||||
final versionedArtifact = File('${localWorkspace.path}/notes/hello.v2.txt');
|
||||
expect(await versionedArtifact.readAsString(), 'artifact body');
|
||||
final snapshot = await controller.loadAssistantArtifactSnapshot(
|
||||
sessionKey: 'session-1',
|
||||
sessionKey: 'draft:test-task-a',
|
||||
);
|
||||
expect(snapshot.resultEntries.map((entry) => entry.relativePath), <String>[
|
||||
'notes/hello.v2.txt',
|
||||
@ -291,7 +325,7 @@ void main() {
|
||||
);
|
||||
expect(
|
||||
controller
|
||||
.requireTaskThreadForSessionInternal('session-1')
|
||||
.requireTaskThreadForSessionInternal('draft:test-task-a')
|
||||
.lastArtifactSyncStatus,
|
||||
'synced',
|
||||
);
|
||||
@ -317,9 +351,9 @@ void main() {
|
||||
await staleArtifact.writeAsString('stale task output');
|
||||
|
||||
controller.upsertTaskThreadInternal(
|
||||
'session-1',
|
||||
'draft:test-task-a',
|
||||
workspaceBinding: WorkspaceBinding(
|
||||
workspaceId: 'session-1',
|
||||
workspaceId: 'draft:test-task-a',
|
||||
workspaceKind: WorkspaceKind.localFs,
|
||||
workspacePath: localWorkspace.path,
|
||||
displayPath: localWorkspace.path,
|
||||
@ -346,12 +380,12 @@ void main() {
|
||||
);
|
||||
|
||||
await controller.persistGoTaskArtifactsForSessionInternal(
|
||||
'session-1',
|
||||
'draft:test-task-a',
|
||||
result,
|
||||
);
|
||||
|
||||
final snapshot = await controller.loadAssistantArtifactSnapshot(
|
||||
sessionKey: 'session-1',
|
||||
sessionKey: 'draft:test-task-a',
|
||||
);
|
||||
final currentRelativePaths = snapshot.resultEntries
|
||||
.map((entry) => entry.relativePath)
|
||||
@ -372,7 +406,7 @@ void main() {
|
||||
previewable: true,
|
||||
workspacePath: localWorkspace.path,
|
||||
),
|
||||
sessionKey: 'session-1',
|
||||
sessionKey: 'draft:test-task-a',
|
||||
);
|
||||
expect(stalePreview.kind, AssistantArtifactPreviewKind.markdown);
|
||||
expect(stalePreview.content, 'stale task output');
|
||||
@ -412,9 +446,9 @@ void main() {
|
||||
});
|
||||
|
||||
controller.upsertTaskThreadInternal(
|
||||
'session-1',
|
||||
'draft:test-task-a',
|
||||
workspaceBinding: WorkspaceBinding(
|
||||
workspaceId: 'session-1',
|
||||
workspaceId: 'draft:test-task-a',
|
||||
workspaceKind: WorkspaceKind.localFs,
|
||||
workspacePath: localWorkspace.path,
|
||||
displayPath: localWorkspace.path,
|
||||
@ -444,7 +478,7 @@ void main() {
|
||||
final clientFactory = _proxiedClientFactory(server.port);
|
||||
await HttpOverrides.runZoned(() async {
|
||||
await controller.persistGoTaskArtifactsForSessionInternal(
|
||||
'session-1',
|
||||
'draft:test-task-a',
|
||||
result,
|
||||
);
|
||||
}, createHttpClient: clientFactory);
|
||||
@ -453,7 +487,7 @@ void main() {
|
||||
expect(await artifact.readAsString(), 'downloaded artifact body');
|
||||
expect(observedAuthorization, 'Bearer bridge-token');
|
||||
final snapshot = await controller.loadAssistantArtifactSnapshot(
|
||||
sessionKey: 'session-1',
|
||||
sessionKey: 'draft:test-task-a',
|
||||
);
|
||||
expect(
|
||||
snapshot.fileEntries.map((entry) => entry.relativePath),
|
||||
@ -461,7 +495,7 @@ void main() {
|
||||
);
|
||||
expect(
|
||||
controller
|
||||
.requireTaskThreadForSessionInternal('session-1')
|
||||
.requireTaskThreadForSessionInternal('draft:test-task-a')
|
||||
.lastArtifactSyncStatus,
|
||||
'synced',
|
||||
);
|
||||
@ -650,9 +684,9 @@ void main() {
|
||||
}
|
||||
});
|
||||
controller.upsertTaskThreadInternal(
|
||||
'session-1',
|
||||
'draft:test-task-a',
|
||||
workspaceBinding: WorkspaceBinding(
|
||||
workspaceId: 'session-1',
|
||||
workspaceId: 'draft:test-task-a',
|
||||
workspaceKind: WorkspaceKind.localFs,
|
||||
workspacePath: localWorkspace.path,
|
||||
displayPath: localWorkspace.path,
|
||||
@ -670,7 +704,7 @@ void main() {
|
||||
'relativePath': 'reports/resume.bin',
|
||||
'downloadUrl':
|
||||
'http://xworkmate-bridge.svc.plus:${server.port}/artifacts/openclaw/download'
|
||||
'?sessionKey=session-1&runId=run-1&relativePath=reports%2Fresume.bin'
|
||||
'?sessionKey=draft:test-task-a&runId=run-1&relativePath=reports%2Fresume.bin'
|
||||
'&expires=9999999999&sig=test-signature',
|
||||
'contentType': 'application/octet-stream',
|
||||
'sizeBytes': body.length,
|
||||
@ -686,7 +720,7 @@ void main() {
|
||||
final clientFactory = _proxiedClientFactory(server.port);
|
||||
await HttpOverrides.runZoned(() async {
|
||||
await controller.persistGoTaskArtifactsForSessionInternal(
|
||||
'session-1',
|
||||
'draft:test-task-a',
|
||||
result,
|
||||
);
|
||||
}, createHttpClient: clientFactory);
|
||||
@ -699,7 +733,7 @@ void main() {
|
||||
);
|
||||
expect(
|
||||
controller
|
||||
.requireTaskThreadForSessionInternal('session-1')
|
||||
.requireTaskThreadForSessionInternal('draft:test-task-a')
|
||||
.lastArtifactSyncStatus,
|
||||
'synced',
|
||||
);
|
||||
@ -754,9 +788,9 @@ void main() {
|
||||
}
|
||||
});
|
||||
controller.upsertTaskThreadInternal(
|
||||
'session-1',
|
||||
'draft:test-task-a',
|
||||
workspaceBinding: WorkspaceBinding(
|
||||
workspaceId: 'session-1',
|
||||
workspaceId: 'draft:test-task-a',
|
||||
workspaceKind: WorkspaceKind.localFs,
|
||||
workspacePath: localWorkspace.path,
|
||||
displayPath: localWorkspace.path,
|
||||
@ -786,7 +820,7 @@ void main() {
|
||||
final clientFactory = _proxiedClientFactory(server.port);
|
||||
await HttpOverrides.runZoned(() async {
|
||||
await controller.persistGoTaskArtifactsForSessionInternal(
|
||||
'session-1',
|
||||
'draft:test-task-a',
|
||||
result,
|
||||
);
|
||||
}, createHttpClient: clientFactory);
|
||||
@ -798,7 +832,7 @@ void main() {
|
||||
);
|
||||
expect(
|
||||
controller
|
||||
.requireTaskThreadForSessionInternal('session-1')
|
||||
.requireTaskThreadForSessionInternal('draft:test-task-a')
|
||||
.lastArtifactSyncStatus,
|
||||
'synced',
|
||||
);
|
||||
@ -837,9 +871,9 @@ void main() {
|
||||
}
|
||||
});
|
||||
controller.upsertTaskThreadInternal(
|
||||
'session-1',
|
||||
'draft:test-task-a',
|
||||
workspaceBinding: WorkspaceBinding(
|
||||
workspaceId: 'session-1',
|
||||
workspaceId: 'draft:test-task-a',
|
||||
workspaceKind: WorkspaceKind.localFs,
|
||||
workspacePath: localWorkspace.path,
|
||||
displayPath: localWorkspace.path,
|
||||
@ -880,7 +914,7 @@ void main() {
|
||||
final clientFactory = _proxiedClientFactory(server.port);
|
||||
await HttpOverrides.runZoned(() async {
|
||||
await controller.persistGoTaskArtifactsForSessionInternal(
|
||||
'session-1',
|
||||
'draft:test-task-a',
|
||||
result,
|
||||
);
|
||||
}, createHttpClient: clientFactory);
|
||||
@ -898,7 +932,7 @@ void main() {
|
||||
isFalse,
|
||||
);
|
||||
final snapshot = await controller.loadAssistantArtifactSnapshot(
|
||||
sessionKey: 'session-1',
|
||||
sessionKey: 'draft:test-task-a',
|
||||
);
|
||||
expect(
|
||||
snapshot.fileEntries.map((entry) => entry.relativePath),
|
||||
@ -906,7 +940,7 @@ void main() {
|
||||
);
|
||||
expect(
|
||||
controller
|
||||
.requireTaskThreadForSessionInternal('session-1')
|
||||
.requireTaskThreadForSessionInternal('draft:test-task-a')
|
||||
.lastArtifactSyncStatus,
|
||||
'partial',
|
||||
);
|
||||
@ -939,9 +973,9 @@ void main() {
|
||||
}
|
||||
});
|
||||
controller.upsertTaskThreadInternal(
|
||||
'session-1',
|
||||
'draft:test-task-a',
|
||||
workspaceBinding: WorkspaceBinding(
|
||||
workspaceId: 'session-1',
|
||||
workspaceId: 'draft:test-task-a',
|
||||
workspaceKind: WorkspaceKind.localFs,
|
||||
workspacePath: localWorkspace.path,
|
||||
displayPath: localWorkspace.path,
|
||||
@ -974,7 +1008,7 @@ void main() {
|
||||
final clientFactory = _proxiedClientFactory(server.port);
|
||||
await HttpOverrides.runZoned(() async {
|
||||
await controller.persistGoTaskArtifactsForSessionInternal(
|
||||
'session-1',
|
||||
'draft:test-task-a',
|
||||
result,
|
||||
);
|
||||
}, createHttpClient: clientFactory);
|
||||
@ -990,7 +1024,7 @@ void main() {
|
||||
expect(leftovers, isEmpty);
|
||||
expect(
|
||||
controller
|
||||
.requireTaskThreadForSessionInternal('session-1')
|
||||
.requireTaskThreadForSessionInternal('draft:test-task-a')
|
||||
.lastArtifactSyncStatus,
|
||||
'download-failed',
|
||||
);
|
||||
@ -1013,9 +1047,9 @@ void main() {
|
||||
}
|
||||
});
|
||||
controller.upsertTaskThreadInternal(
|
||||
'session-1',
|
||||
'draft:test-task-a',
|
||||
workspaceBinding: WorkspaceBinding(
|
||||
workspaceId: 'session-1',
|
||||
workspaceId: 'draft:test-task-a',
|
||||
workspaceKind: WorkspaceKind.localFs,
|
||||
workspacePath: localWorkspace.path,
|
||||
displayPath: localWorkspace.path,
|
||||
@ -1035,13 +1069,13 @@ void main() {
|
||||
);
|
||||
|
||||
await controller.persistGoTaskArtifactsForSessionInternal(
|
||||
'session-1',
|
||||
'draft:test-task-a',
|
||||
result,
|
||||
);
|
||||
|
||||
expect(await localWorkspace.list(recursive: true).toList(), isEmpty);
|
||||
final thread = controller.requireTaskThreadForSessionInternal(
|
||||
'session-1',
|
||||
'draft:test-task-a',
|
||||
);
|
||||
expect(thread.lastArtifactSyncStatus, 'no-exported-artifacts');
|
||||
expect(thread.lastArtifactSyncAtMs, greaterThan(0));
|
||||
@ -1065,9 +1099,9 @@ void main() {
|
||||
final staleArtifact = File('${localWorkspace.path}/old-task-report.md');
|
||||
await staleArtifact.writeAsString('stale task output');
|
||||
controller.upsertTaskThreadInternal(
|
||||
'session-1',
|
||||
'draft:test-task-a',
|
||||
workspaceBinding: WorkspaceBinding(
|
||||
workspaceId: 'session-1',
|
||||
workspaceId: 'draft:test-task-a',
|
||||
workspaceKind: WorkspaceKind.localFs,
|
||||
workspacePath: localWorkspace.path,
|
||||
displayPath: localWorkspace.path,
|
||||
@ -1086,18 +1120,18 @@ void main() {
|
||||
);
|
||||
|
||||
await controller.persistGoTaskArtifactsForSessionInternal(
|
||||
'session-1',
|
||||
'draft:test-task-a',
|
||||
result,
|
||||
);
|
||||
|
||||
expect(
|
||||
controller
|
||||
.requireTaskThreadForSessionInternal('session-1')
|
||||
.requireTaskThreadForSessionInternal('draft:test-task-a')
|
||||
.lastArtifactSyncStatus,
|
||||
'no-artifacts',
|
||||
);
|
||||
final snapshot = await controller.loadAssistantArtifactSnapshot(
|
||||
sessionKey: 'session-1',
|
||||
sessionKey: 'draft:test-task-a',
|
||||
);
|
||||
expect(snapshot.resultEntries, isEmpty);
|
||||
expect(
|
||||
@ -1124,9 +1158,9 @@ void main() {
|
||||
});
|
||||
|
||||
controller.upsertTaskThreadInternal(
|
||||
'session-1',
|
||||
'draft:test-task-a',
|
||||
workspaceBinding: WorkspaceBinding(
|
||||
workspaceId: 'session-1',
|
||||
workspaceId: 'draft:test-task-a',
|
||||
workspaceKind: WorkspaceKind.localFs,
|
||||
workspacePath: localWorkspace.path,
|
||||
displayPath: localWorkspace.path,
|
||||
@ -1153,7 +1187,7 @@ void main() {
|
||||
);
|
||||
|
||||
await controller.persistGoTaskArtifactsForSessionInternal(
|
||||
'session-1',
|
||||
'draft:test-task-a',
|
||||
result,
|
||||
);
|
||||
|
||||
@ -1163,7 +1197,7 @@ void main() {
|
||||
);
|
||||
expect(
|
||||
controller
|
||||
.requireTaskThreadForSessionInternal('session-1')
|
||||
.requireTaskThreadForSessionInternal('draft:test-task-a')
|
||||
.lastArtifactSyncStatus,
|
||||
'no-artifacts',
|
||||
);
|
||||
|
||||
@ -43,7 +43,7 @@ void main() {
|
||||
);
|
||||
addTearDown(controller.dispose);
|
||||
|
||||
await controller.sessionsController.switchSession('session-1');
|
||||
await controller.sessionsController.switchSession('draft:test-task-a');
|
||||
await controller.setAssistantExecutionTarget(
|
||||
AssistantExecutionTarget.gateway,
|
||||
);
|
||||
@ -75,7 +75,7 @@ void main() {
|
||||
);
|
||||
addTearDown(controller.dispose);
|
||||
|
||||
await controller.sessionsController.switchSession('session-1');
|
||||
await controller.sessionsController.switchSession('draft:test-task-a');
|
||||
await controller.setAssistantExecutionTarget(
|
||||
AssistantExecutionTarget.gateway,
|
||||
);
|
||||
@ -108,7 +108,7 @@ void main() {
|
||||
);
|
||||
addTearDown(controller.dispose);
|
||||
|
||||
await controller.sessionsController.switchSession('session-1');
|
||||
await controller.sessionsController.switchSession('draft:test-task-a');
|
||||
await controller.setAssistantExecutionTarget(
|
||||
AssistantExecutionTarget.gateway,
|
||||
);
|
||||
@ -271,7 +271,7 @@ void main() {
|
||||
final controller = await _isolatedController();
|
||||
addTearDown(controller.dispose);
|
||||
|
||||
await controller.sessionsController.switchSession('session-1');
|
||||
await controller.sessionsController.switchSession('draft:test-task-a');
|
||||
await controller.setAssistantExecutionTarget(
|
||||
AssistantExecutionTarget.gateway,
|
||||
);
|
||||
@ -298,7 +298,7 @@ void main() {
|
||||
final controller = await _isolatedController();
|
||||
addTearDown(controller.dispose);
|
||||
|
||||
await controller.sessionsController.switchSession('session-1');
|
||||
await controller.sessionsController.switchSession('draft:test-task-a');
|
||||
await controller.setAssistantExecutionTarget(
|
||||
AssistantExecutionTarget.gateway,
|
||||
);
|
||||
@ -320,7 +320,7 @@ void main() {
|
||||
final controller = await _isolatedController();
|
||||
addTearDown(controller.dispose);
|
||||
|
||||
await controller.sessionsController.switchSession('session-1');
|
||||
await controller.sessionsController.switchSession('draft:test-task-a');
|
||||
await controller.setAssistantExecutionTarget(
|
||||
AssistantExecutionTarget.gateway,
|
||||
);
|
||||
@ -350,7 +350,7 @@ void main() {
|
||||
final controller = await _isolatedController();
|
||||
addTearDown(controller.dispose);
|
||||
|
||||
await controller.sessionsController.switchSession('session-1');
|
||||
await controller.sessionsController.switchSession('draft:test-task-a');
|
||||
await controller.setAssistantExecutionTarget(
|
||||
AssistantExecutionTarget.gateway,
|
||||
);
|
||||
@ -380,7 +380,7 @@ void main() {
|
||||
final controller = await _isolatedController();
|
||||
addTearDown(controller.dispose);
|
||||
|
||||
await controller.sessionsController.switchSession('session-1');
|
||||
await controller.sessionsController.switchSession('draft:test-task-a');
|
||||
await controller.setAssistantExecutionTarget(
|
||||
AssistantExecutionTarget.gateway,
|
||||
);
|
||||
@ -407,7 +407,7 @@ void main() {
|
||||
final controller = await _isolatedController();
|
||||
addTearDown(controller.dispose);
|
||||
|
||||
await controller.sessionsController.switchSession('session-1');
|
||||
await controller.sessionsController.switchSession('draft:test-task-a');
|
||||
await controller.setAssistantExecutionTarget(
|
||||
AssistantExecutionTarget.gateway,
|
||||
);
|
||||
|
||||
@ -82,7 +82,7 @@ void main() {
|
||||
);
|
||||
addTearDown(controller.dispose);
|
||||
|
||||
await controller.sessionsController.switchSession('session-1');
|
||||
await controller.sessionsController.switchSession('draft:test-task-a');
|
||||
|
||||
expect(controller.currentAssistantExecutionTarget.isAgent, isTrue);
|
||||
expect(
|
||||
@ -95,14 +95,14 @@ void main() {
|
||||
);
|
||||
|
||||
final record = controller.requireTaskThreadForSessionInternal(
|
||||
'session-1',
|
||||
'draft:test-task-a',
|
||||
);
|
||||
expect(
|
||||
record.executionBinding.executionMode,
|
||||
ThreadExecutionMode.gateway,
|
||||
);
|
||||
expect(
|
||||
controller.assistantProviderForSession('session-1'),
|
||||
controller.assistantProviderForSession('draft:test-task-a'),
|
||||
SingleAgentProvider.openclaw,
|
||||
);
|
||||
},
|
||||
@ -135,22 +135,25 @@ void main() {
|
||||
addTearDown(controller.dispose);
|
||||
controller.resolvedUserHomeDirectoryInternal = localHome.path;
|
||||
|
||||
controller.upsertTaskThreadInternal(
|
||||
'main',
|
||||
executionTarget: AssistantExecutionTarget.gateway,
|
||||
selectedProvider: SingleAgentProvider.openclaw,
|
||||
selectedProviderSource: ThreadSelectionSource.explicit,
|
||||
expect(
|
||||
() => controller.upsertTaskThreadInternal(
|
||||
'main',
|
||||
executionTarget: AssistantExecutionTarget.gateway,
|
||||
selectedProvider: SingleAgentProvider.openclaw,
|
||||
selectedProviderSource: ThreadSelectionSource.explicit,
|
||||
),
|
||||
throwsStateError,
|
||||
);
|
||||
|
||||
expect(
|
||||
controller.assistantExecutionTargetForSession('fresh-task'),
|
||||
controller.assistantExecutionTargetForSession('draft:fresh-task'),
|
||||
AssistantExecutionTarget.agent,
|
||||
);
|
||||
|
||||
await controller.switchSession('fresh-task');
|
||||
await controller.switchSession('draft:fresh-task');
|
||||
|
||||
final freshThread = controller.requireTaskThreadForSessionInternal(
|
||||
'fresh-task',
|
||||
'draft:fresh-task',
|
||||
);
|
||||
expect(
|
||||
freshThread.executionBinding.executionMode,
|
||||
@ -158,7 +161,7 @@ void main() {
|
||||
);
|
||||
expect(
|
||||
freshThread.workspaceBinding.workspacePath,
|
||||
endsWith('/.xworkmate/threads/fresh-task'),
|
||||
endsWith('/.xworkmate/threads/draft-fresh-task'),
|
||||
);
|
||||
},
|
||||
);
|
||||
@ -182,6 +185,121 @@ void main() {
|
||||
expect(second, isNot(first));
|
||||
});
|
||||
|
||||
test('navigateHome does not select the runtime main session key', () async {
|
||||
final localHome = await Directory.systemTemp.createTemp(
|
||||
'xworkmate-no-runtime-main-home-',
|
||||
);
|
||||
addTearDown(() async {
|
||||
if (await localHome.exists()) {
|
||||
await localHome.delete(recursive: true);
|
||||
}
|
||||
});
|
||||
final controller = AppController(
|
||||
environmentOverride: const <String, String>{},
|
||||
);
|
||||
addTearDown(controller.dispose);
|
||||
controller.resolvedUserHomeDirectoryInternal = localHome.path;
|
||||
controller.runtimeInternal.snapshotInternal = controller
|
||||
.runtimeInternal
|
||||
.snapshot
|
||||
.copyWith(mainSessionKey: 'session-1');
|
||||
|
||||
const taskKey = 'draft:test-home-task';
|
||||
await controller.switchSession(taskKey);
|
||||
|
||||
controller.navigateHome();
|
||||
await Future<void>.delayed(Duration.zero);
|
||||
|
||||
expect(controller.currentSessionKey, taskKey);
|
||||
expect(
|
||||
controller.assistantWorkspacePathForSession(taskKey),
|
||||
endsWith('/.xworkmate/threads/draft-test-home-task'),
|
||||
);
|
||||
expect(controller.assistantWorkspacePathForSession('session-1'), isEmpty);
|
||||
expect(controller.taskThreadForSessionInternal('session-1'), isNull);
|
||||
});
|
||||
|
||||
test(
|
||||
'refreshSessions allocates an app task instead of runtime main when current is stale',
|
||||
() async {
|
||||
final localHome = await Directory.systemTemp.createTemp(
|
||||
'xworkmate-refresh-no-session-one-',
|
||||
);
|
||||
addTearDown(() async {
|
||||
if (await localHome.exists()) {
|
||||
await localHome.delete(recursive: true);
|
||||
}
|
||||
});
|
||||
final controller = AppController(
|
||||
environmentOverride: const <String, String>{},
|
||||
);
|
||||
addTearDown(controller.dispose);
|
||||
controller.resolvedUserHomeDirectoryInternal = localHome.path;
|
||||
controller.runtimeInternal.snapshotInternal = controller
|
||||
.runtimeInternal
|
||||
.snapshot
|
||||
.copyWith(mainSessionKey: 'session-1');
|
||||
|
||||
await controller.refreshSessions();
|
||||
|
||||
expect(controller.currentSessionKey, startsWith('draft:'));
|
||||
expect(controller.currentSessionKey, isNot('session-1'));
|
||||
expect(controller.currentSessionKey, isNot('main'));
|
||||
expect(
|
||||
controller.assistantWorkspacePathForSession(
|
||||
controller.currentSessionKey,
|
||||
),
|
||||
contains('/.xworkmate/threads/draft-'),
|
||||
);
|
||||
expect(
|
||||
controller.assistantWorkspacePathForSession('session-1'),
|
||||
isEmpty,
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
test('assistant task list ignores runtime sessions from the gateway', () {
|
||||
final controller = AppController(
|
||||
environmentOverride: const <String, String>{},
|
||||
);
|
||||
addTearDown(controller.dispose);
|
||||
controller.sessionsControllerInternal.sessionsInternal =
|
||||
const <GatewaySessionSummary>[
|
||||
GatewaySessionSummary(
|
||||
key: 'session-1',
|
||||
kind: 'assistant',
|
||||
displayName: 'runtime session',
|
||||
surface: 'Assistant',
|
||||
subject: null,
|
||||
room: null,
|
||||
space: null,
|
||||
updatedAtMs: 1,
|
||||
sessionId: 'session-1',
|
||||
systemSent: false,
|
||||
abortedLastRun: false,
|
||||
thinkingLevel: null,
|
||||
verboseLevel: null,
|
||||
inputTokens: null,
|
||||
outputTokens: null,
|
||||
totalTokens: null,
|
||||
model: null,
|
||||
contextTokens: null,
|
||||
derivedTitle: null,
|
||||
lastMessagePreview: null,
|
||||
),
|
||||
];
|
||||
controller.initializeAssistantThreadContext(
|
||||
'draft:test-visible-task',
|
||||
executionTarget: AssistantExecutionTarget.agent,
|
||||
messageViewMode: AssistantMessageViewMode.rendered,
|
||||
);
|
||||
|
||||
final keys = controller.assistantSessions.map((item) => item.key);
|
||||
|
||||
expect(keys, contains('draft:test-visible-task'));
|
||||
expect(keys, isNot(contains('session-1')));
|
||||
});
|
||||
|
||||
test(
|
||||
'returns unspecified when a saved provider is no longer in the current catalog',
|
||||
() {
|
||||
@ -235,17 +353,17 @@ void main() {
|
||||
);
|
||||
addTearDown(controller.dispose);
|
||||
|
||||
await controller.sessionsController.switchSession('session-1');
|
||||
await controller.sessionsController.switchSession('draft:test-task-a');
|
||||
await controller.setAssistantExecutionTarget(
|
||||
AssistantExecutionTarget.gateway,
|
||||
);
|
||||
|
||||
final record = controller.requireTaskThreadForSessionInternal(
|
||||
'session-1',
|
||||
'draft:test-task-a',
|
||||
);
|
||||
|
||||
expect(
|
||||
controller.assistantExecutionTargetForSession('session-1'),
|
||||
controller.assistantExecutionTargetForSession('draft:test-task-a'),
|
||||
AssistantExecutionTarget.gateway,
|
||||
);
|
||||
expect(record.executionBinding.providerId, isEmpty);
|
||||
@ -269,13 +387,13 @@ void main() {
|
||||
);
|
||||
addTearDown(controller.dispose);
|
||||
|
||||
await controller.sessionsController.switchSession('session-1');
|
||||
await controller.sessionsController.switchSession('draft:test-task-a');
|
||||
await controller.setAssistantExecutionTarget(
|
||||
AssistantExecutionTarget.gateway,
|
||||
);
|
||||
|
||||
final routing = controller.buildExternalAcpRoutingForSessionInternal(
|
||||
'session-1',
|
||||
'draft:test-task-a',
|
||||
);
|
||||
|
||||
expect(routing.mode, ExternalCodeAgentAcpRoutingMode.explicit);
|
||||
@ -405,7 +523,7 @@ void main() {
|
||||
);
|
||||
addTearDown(controller.dispose);
|
||||
|
||||
await controller.sessionsController.switchSession('session-1');
|
||||
await controller.sessionsController.switchSession('draft:test-task-a');
|
||||
await Future<void>.delayed(const Duration(milliseconds: 200));
|
||||
|
||||
expect(controller.assistantProviderCatalog, isEmpty);
|
||||
@ -467,7 +585,7 @@ void main() {
|
||||
);
|
||||
addTearDown(controller.dispose);
|
||||
|
||||
await controller.sessionsController.switchSession('session-1');
|
||||
await controller.sessionsController.switchSession('draft:test-task-a');
|
||||
await controller.setAssistantExecutionTarget(
|
||||
AssistantExecutionTarget.gateway,
|
||||
);
|
||||
@ -580,7 +698,7 @@ void main() {
|
||||
),
|
||||
);
|
||||
|
||||
await controller.sessionsController.switchSession('session-1');
|
||||
await controller.sessionsController.switchSession('draft:test-task-a');
|
||||
await controller.setAssistantExecutionTarget(
|
||||
AssistantExecutionTarget.agent,
|
||||
);
|
||||
@ -618,14 +736,16 @@ void main() {
|
||||
);
|
||||
addTearDown(controller.dispose);
|
||||
|
||||
await controller.sessionsController.switchSession('session-1');
|
||||
await controller.sessionsController.switchSession('draft:test-task-a');
|
||||
expect(
|
||||
controller.hasCommittedUserTurnForGatewaySessionInternal('session-1'),
|
||||
controller.hasCommittedUserTurnForGatewaySessionInternal(
|
||||
'draft:test-task-a',
|
||||
),
|
||||
isFalse,
|
||||
);
|
||||
|
||||
controller.appendLocalSessionMessageInternal(
|
||||
'session-1',
|
||||
'draft:test-task-a',
|
||||
GatewayChatMessage(
|
||||
id: 'error-1',
|
||||
role: 'assistant',
|
||||
@ -641,12 +761,14 @@ void main() {
|
||||
);
|
||||
|
||||
expect(
|
||||
controller.hasCommittedUserTurnForGatewaySessionInternal('session-1'),
|
||||
controller.hasCommittedUserTurnForGatewaySessionInternal(
|
||||
'draft:test-task-a',
|
||||
),
|
||||
isFalse,
|
||||
);
|
||||
|
||||
controller.appendLocalSessionMessageInternal(
|
||||
'session-1',
|
||||
'draft:test-task-a',
|
||||
GatewayChatMessage(
|
||||
id: 'assistant-1',
|
||||
role: 'assistant',
|
||||
@ -662,12 +784,14 @@ void main() {
|
||||
);
|
||||
|
||||
expect(
|
||||
controller.hasCommittedUserTurnForGatewaySessionInternal('session-1'),
|
||||
controller.hasCommittedUserTurnForGatewaySessionInternal(
|
||||
'draft:test-task-a',
|
||||
),
|
||||
isFalse,
|
||||
);
|
||||
|
||||
controller.appendLocalSessionMessageInternal(
|
||||
'session-1',
|
||||
'draft:test-task-a',
|
||||
GatewayChatMessage(
|
||||
id: 'user-1',
|
||||
role: 'user',
|
||||
@ -683,7 +807,9 @@ void main() {
|
||||
);
|
||||
|
||||
expect(
|
||||
controller.hasCommittedUserTurnForGatewaySessionInternal('session-1'),
|
||||
controller.hasCommittedUserTurnForGatewaySessionInternal(
|
||||
'draft:test-task-a',
|
||||
),
|
||||
isTrue,
|
||||
);
|
||||
},
|
||||
@ -694,7 +820,7 @@ void main() {
|
||||
final controller = _connectedController(fakeGoTaskService);
|
||||
addTearDown(controller.dispose);
|
||||
|
||||
await controller.sessionsController.switchSession('session-1');
|
||||
await controller.sessionsController.switchSession('draft:test-task-a');
|
||||
|
||||
await controller.sendChatMessage('first turn');
|
||||
|
||||
@ -714,8 +840,8 @@ void main() {
|
||||
final fakeGoTaskService = _RecordingGoTaskServiceClient()
|
||||
..updatesBeforeNextOutcome.add(
|
||||
const GoTaskServiceUpdate(
|
||||
sessionId: 'session-1',
|
||||
threadId: 'session-1',
|
||||
sessionId: 'draft:test-task-a',
|
||||
threadId: 'draft:test-task-a',
|
||||
turnId: 'turn-1',
|
||||
type: 'delta',
|
||||
text: 'partial output that must not persist',
|
||||
@ -747,7 +873,7 @@ void main() {
|
||||
addTearDown(controller.dispose);
|
||||
controller.resolvedUserHomeDirectoryInternal = localWorkspace.path;
|
||||
|
||||
await controller.sessionsController.switchSession('session-1');
|
||||
await controller.sessionsController.switchSession('draft:test-task-a');
|
||||
|
||||
await controller.sendChatMessage('first turn');
|
||||
|
||||
@ -755,7 +881,7 @@ void main() {
|
||||
expect(fakeGoTaskService.requests.single.resumeSession, isFalse);
|
||||
expect(
|
||||
controller
|
||||
.taskThreadForSessionInternal('session-1')
|
||||
.taskThreadForSessionInternal('draft:test-task-a')
|
||||
?.lifecycleState
|
||||
.status,
|
||||
'ready',
|
||||
@ -770,7 +896,7 @@ void main() {
|
||||
);
|
||||
expect(
|
||||
controller
|
||||
.taskThreadForSessionInternal('session-1')
|
||||
.taskThreadForSessionInternal('draft:test-task-a')
|
||||
?.lastArtifactSyncStatus,
|
||||
'failed',
|
||||
);
|
||||
@ -781,12 +907,14 @@ void main() {
|
||||
expect(fakeGoTaskService.requests.last.resumeSession, isFalse);
|
||||
await _waitForLastChatMessageText(controller, '全部 6 个文件已生成 ✅');
|
||||
expect(controller.chatMessages.last.text, '全部 6 个文件已生成 ✅');
|
||||
final thread = controller.taskThreadForSessionInternal('session-1');
|
||||
final thread = controller.taskThreadForSessionInternal(
|
||||
'draft:test-task-a',
|
||||
);
|
||||
expect(thread?.lifecycleState.status, 'ready');
|
||||
expect(thread?.lastArtifactSyncStatus, 'synced');
|
||||
expect(thread?.lastArtifactSyncAtMs, greaterThan(0));
|
||||
final workspacePath = controller.assistantWorkspacePathForSession(
|
||||
'session-1',
|
||||
'draft:test-task-a',
|
||||
);
|
||||
for (final artifact in _generatedArtifactPayloads()) {
|
||||
final relativePath = artifact['relativePath']! as String;
|
||||
@ -822,14 +950,14 @@ void main() {
|
||||
final controller = _connectedController(fakeGoTaskService);
|
||||
addTearDown(controller.dispose);
|
||||
|
||||
await controller.sessionsController.switchSession('session-1');
|
||||
await controller.sessionsController.switchSession('draft:test-task-a');
|
||||
|
||||
await controller.sendChatMessage('first turn');
|
||||
|
||||
expect(fakeGoTaskService.requests, hasLength(1));
|
||||
expect(fakeGoTaskService.requests.single.resumeSession, isFalse);
|
||||
final failedThread = controller.taskThreadForSessionInternal(
|
||||
'session-1',
|
||||
'draft:test-task-a',
|
||||
);
|
||||
expect(failedThread?.lifecycleState.status, 'ready');
|
||||
expect(
|
||||
@ -855,7 +983,9 @@ void main() {
|
||||
controller.chatMessages.last.text,
|
||||
'retried from a confirmed new start',
|
||||
);
|
||||
final thread = controller.taskThreadForSessionInternal('session-1');
|
||||
final thread = controller.taskThreadForSessionInternal(
|
||||
'draft:test-task-a',
|
||||
);
|
||||
expect(thread?.lifecycleState.status, 'ready');
|
||||
expect(thread?.lifecycleState.lastResultCode, 'success');
|
||||
},
|
||||
@ -877,8 +1007,8 @@ void main() {
|
||||
final fakeGoTaskService = _RecordingGoTaskServiceClient()
|
||||
..updatesBeforeNextOutcome.add(
|
||||
const GoTaskServiceUpdate(
|
||||
sessionId: 'session-1',
|
||||
threadId: 'session-1',
|
||||
sessionId: 'draft:test-task-a',
|
||||
threadId: 'draft:test-task-a',
|
||||
turnId: 'turn-1',
|
||||
type: 'delta',
|
||||
text: 'guard partial output must not persist',
|
||||
@ -910,7 +1040,7 @@ void main() {
|
||||
addTearDown(controller.dispose);
|
||||
controller.resolvedUserHomeDirectoryInternal = localWorkspace.path;
|
||||
|
||||
await controller.sessionsController.switchSession('session-1');
|
||||
await controller.sessionsController.switchSession('draft:test-task-a');
|
||||
|
||||
await controller.sendChatMessage('first turn');
|
||||
await controller.sendChatMessage('follow up');
|
||||
@ -929,7 +1059,9 @@ void main() {
|
||||
isNot(contains('guard partial output must not persist')),
|
||||
);
|
||||
|
||||
final thread = controller.taskThreadForSessionInternal('session-1');
|
||||
final thread = controller.taskThreadForSessionInternal(
|
||||
'draft:test-task-a',
|
||||
);
|
||||
expect(thread?.lifecycleState.status, 'ready');
|
||||
expect(thread?.lastArtifactSyncStatus, 'no-exported-artifacts');
|
||||
expect(thread?.lastArtifactSyncAtMs, greaterThan(0));
|
||||
@ -952,8 +1084,8 @@ void main() {
|
||||
final fakeGoTaskService = _RecordingGoTaskServiceClient()
|
||||
..updatesBeforeNextOutcome.add(
|
||||
const GoTaskServiceUpdate(
|
||||
sessionId: 'session-1',
|
||||
threadId: 'session-1',
|
||||
sessionId: 'draft:test-task-a',
|
||||
threadId: 'draft:test-task-a',
|
||||
turnId: 'turn-1',
|
||||
type: 'delta',
|
||||
text: guardMessage,
|
||||
@ -985,7 +1117,7 @@ void main() {
|
||||
addTearDown(controller.dispose);
|
||||
controller.resolvedUserHomeDirectoryInternal = localWorkspace.path;
|
||||
|
||||
await controller.sessionsController.switchSession('session-1');
|
||||
await controller.sessionsController.switchSession('draft:test-task-a');
|
||||
await controller.sendChatMessage('create files');
|
||||
|
||||
final transcript = controller.chatMessages
|
||||
@ -993,7 +1125,9 @@ void main() {
|
||||
.join('\n');
|
||||
expect(transcript, isNot(contains('未检测到 OpenClaw 本轮导出的实际文件')));
|
||||
expect(transcript, isNot(contains('口头下载声明')));
|
||||
final thread = controller.taskThreadForSessionInternal('session-1');
|
||||
final thread = controller.taskThreadForSessionInternal(
|
||||
'draft:test-task-a',
|
||||
);
|
||||
expect(thread?.lifecycleState.lastResultCode, 'artifact_missing');
|
||||
expect(thread?.lastArtifactSyncStatus, 'no-exported-artifacts');
|
||||
expect(thread?.lastArtifactSyncAtMs, greaterThan(0));
|
||||
@ -1014,8 +1148,8 @@ void main() {
|
||||
final fakeGoTaskService = _RecordingGoTaskServiceClient()
|
||||
..updatesBeforeNextOutcome.add(
|
||||
const GoTaskServiceUpdate(
|
||||
sessionId: 'session-1',
|
||||
threadId: 'session-1',
|
||||
sessionId: 'draft:test-task-a',
|
||||
threadId: 'draft:test-task-a',
|
||||
turnId: 'turn-1',
|
||||
type: 'delta',
|
||||
text: 'handshake partial output must not persist',
|
||||
@ -1047,14 +1181,14 @@ void main() {
|
||||
addTearDown(controller.dispose);
|
||||
controller.resolvedUserHomeDirectoryInternal = localWorkspace.path;
|
||||
|
||||
await controller.sessionsController.switchSession('session-1');
|
||||
await controller.sessionsController.switchSession('draft:test-task-a');
|
||||
|
||||
await controller.sendChatMessage('first turn');
|
||||
|
||||
expect(fakeGoTaskService.requests, hasLength(1));
|
||||
expect(fakeGoTaskService.requests.single.resumeSession, isFalse);
|
||||
final failedThread = controller.taskThreadForSessionInternal(
|
||||
'session-1',
|
||||
'draft:test-task-a',
|
||||
);
|
||||
expect(failedThread?.lifecycleState.status, 'ready');
|
||||
expect(
|
||||
@ -1077,12 +1211,14 @@ void main() {
|
||||
expect(fakeGoTaskService.requests.last.resumeSession, isFalse);
|
||||
await _waitForLastChatMessageText(controller, '全部 6 个文件已生成 ✅');
|
||||
expect(controller.chatMessages.last.text, '全部 6 个文件已生成 ✅');
|
||||
final thread = controller.taskThreadForSessionInternal('session-1');
|
||||
final thread = controller.taskThreadForSessionInternal(
|
||||
'draft:test-task-a',
|
||||
);
|
||||
expect(thread?.lifecycleState.status, 'ready');
|
||||
expect(thread?.lastArtifactSyncStatus, 'synced');
|
||||
expect(thread?.lastArtifactSyncAtMs, greaterThan(0));
|
||||
final workspacePath = controller.assistantWorkspacePathForSession(
|
||||
'session-1',
|
||||
'draft:test-task-a',
|
||||
);
|
||||
for (final artifact in _generatedArtifactPayloads()) {
|
||||
final relativePath = artifact['relativePath']! as String;
|
||||
@ -1103,7 +1239,7 @@ void main() {
|
||||
);
|
||||
addTearDown(controller.dispose);
|
||||
|
||||
await controller.sessionsController.switchSession('session-1');
|
||||
await controller.sessionsController.switchSession('draft:test-task-a');
|
||||
|
||||
final userMessage = GatewayChatMessage(
|
||||
id: 'local-user-1',
|
||||
@ -1129,19 +1265,19 @@ void main() {
|
||||
);
|
||||
|
||||
controller.appendLocalSessionMessageInternal(
|
||||
'session-1',
|
||||
'draft:test-task-a',
|
||||
userMessage,
|
||||
persistInThreadContext: true,
|
||||
);
|
||||
controller.appendLocalSessionMessageInternal(
|
||||
'session-1',
|
||||
'draft:test-task-a',
|
||||
assistantMessage,
|
||||
persistInThreadContext: true,
|
||||
);
|
||||
controller.assistantThreadMessagesInternal['session-1'] =
|
||||
controller.assistantThreadMessagesInternal['draft:test-task-a'] =
|
||||
List<GatewayChatMessage>.from(
|
||||
controller
|
||||
.requireTaskThreadForSessionInternal('session-1')
|
||||
.requireTaskThreadForSessionInternal('draft:test-task-a')
|
||||
.messages,
|
||||
);
|
||||
|
||||
|
||||
@ -5,10 +5,12 @@ import 'package:xworkmate/runtime/runtime_models.dart';
|
||||
void main() {
|
||||
group('Assistant model display', () {
|
||||
test('hides stale model display when no runtime model matches', () async {
|
||||
final controller = AppController(environmentOverride: const <String, String>{});
|
||||
final controller = AppController(
|
||||
environmentOverride: const <String, String>{},
|
||||
);
|
||||
addTearDown(controller.dispose);
|
||||
|
||||
await controller.sessionsController.switchSession('session-1');
|
||||
await controller.sessionsController.switchSession('draft:test-task-a');
|
||||
|
||||
expect(controller.resolvedAssistantModel, isNotEmpty);
|
||||
expect(controller.assistantModelChoices, isEmpty);
|
||||
@ -23,10 +25,12 @@ void main() {
|
||||
test(
|
||||
'shows matched runtime model when gateway catalog is available',
|
||||
() async {
|
||||
final controller = AppController(environmentOverride: const <String, String>{});
|
||||
final controller = AppController(
|
||||
environmentOverride: const <String, String>{},
|
||||
);
|
||||
addTearDown(controller.dispose);
|
||||
|
||||
await controller.sessionsController.switchSession('session-1');
|
||||
await controller.sessionsController.switchSession('draft:test-task-a');
|
||||
await controller.setAssistantExecutionTarget(
|
||||
AssistantExecutionTarget.gateway,
|
||||
);
|
||||
|
||||
Loading…
Reference in New Issue
Block a user