merge: bring installed skill e2e harness into main
This commit is contained in:
commit
bff8ef0973
37
.github/workflows/build-and-release.yml
vendored
37
.github/workflows/build-and-release.yml
vendored
@ -69,7 +69,6 @@ jobs:
|
||||
run: bash ./scripts/ci/compute_release_metadata.sh
|
||||
|
||||
verify:
|
||||
if: ${{ github.event_name == 'pull_request' || github.ref == 'refs/heads/main' }}
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- name: Checkout source
|
||||
@ -89,25 +88,49 @@ jobs:
|
||||
run: bash ./scripts/ci/run_code_analysis.sh
|
||||
|
||||
build:
|
||||
name: Build ${{ matrix.platform }} ${{ matrix.package }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- platform: linux
|
||||
arch: amd64
|
||||
package: deb-rpm
|
||||
runs_on: ubuntu-22.04
|
||||
artifact_name: build-linux-amd64-deb-rpm
|
||||
artifact_paths: |
|
||||
dist/linux/*.deb
|
||||
dist/linux/*.rpm
|
||||
- platform: windows
|
||||
arch: amd64
|
||||
package: msi
|
||||
runs_on: windows-2022
|
||||
artifact_name: build-windows-amd64-msi
|
||||
artifact_paths: |
|
||||
dist/windows/*.msi
|
||||
dist/windows/*.zip
|
||||
- platform: macos
|
||||
arch: arm64
|
||||
package: dmg
|
||||
runs_on: macos-14
|
||||
artifact_name: build-macos-arm64-dmg
|
||||
artifact_paths: |
|
||||
dist/macos/*.dmg
|
||||
- platform: ios
|
||||
arch: arm64
|
||||
package: ipa
|
||||
runs_on: macos-14
|
||||
artifact_name: build-ios-arm64-ipa
|
||||
artifact_paths: |
|
||||
dist/ios/*.ipa
|
||||
dist/ios/*.zip
|
||||
- platform: android
|
||||
arch: arm64
|
||||
package: apk
|
||||
runs_on: ubuntu-22.04
|
||||
artifact_name: build-android-arm64-apk
|
||||
artifact_paths: |
|
||||
dist/android/*.apk
|
||||
runs-on: ${{ matrix.runs_on }}
|
||||
needs:
|
||||
- prepare
|
||||
@ -155,16 +178,8 @@ jobs:
|
||||
- name: Upload build artifacts
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02
|
||||
with:
|
||||
name: build-${{ matrix.platform }}-${{ matrix.arch }}
|
||||
path: |
|
||||
dist/linux/*.deb
|
||||
dist/linux/*.rpm
|
||||
dist/macos/*.dmg
|
||||
dist/windows/*.msi
|
||||
dist/windows/*.zip
|
||||
dist/ios/*.ipa
|
||||
dist/ios/*.zip
|
||||
dist/android/*.apk
|
||||
name: ${{ matrix.artifact_name }}
|
||||
path: ${{ matrix.artifact_paths }}
|
||||
if-no-files-found: error
|
||||
|
||||
release:
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
// ignore_for_file: unused_import, unnecessary_import
|
||||
|
||||
@TestOn('vm')
|
||||
library;
|
||||
|
||||
@ -5,6 +7,7 @@ import 'dart:io';
|
||||
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:xworkmate/app/app_controller.dart';
|
||||
import 'package:xworkmate/runtime/desktop_thread_artifact_service.dart';
|
||||
import 'package:xworkmate/runtime/runtime_models.dart';
|
||||
|
||||
import 'assistant_page_suite_support.dart';
|
||||
@ -12,111 +15,98 @@ import 'assistant_page_suite_support.dart';
|
||||
void main() {
|
||||
group('AssistantPage installed skill E2E harness', () {
|
||||
for (final testCase in installedSkillE2ECasesInternal) {
|
||||
test(
|
||||
'discovers, binds, hands off, and captures ${testCase.skillKey}',
|
||||
() async {
|
||||
final tempDirectory = await Directory.systemTemp.createTemp(
|
||||
'xworkmate-installed-skill-${testCase.skillKey}-',
|
||||
);
|
||||
addTearDown(() async {
|
||||
if (await tempDirectory.exists()) {
|
||||
try {
|
||||
await tempDirectory.delete(recursive: true);
|
||||
} catch (_) {}
|
||||
}
|
||||
});
|
||||
test('discovers, binds, and handoffs ${testCase.skillKey}', () async {
|
||||
final tempDirectory = await Directory.systemTemp.createTemp(
|
||||
'xworkmate-installed-skill-${testCase.skillKey}-',
|
||||
);
|
||||
addTearDown(() async {
|
||||
if (await tempDirectory.exists()) {
|
||||
try {
|
||||
await tempDirectory.delete(recursive: true);
|
||||
} catch (_) {}
|
||||
}
|
||||
});
|
||||
|
||||
final skillsRoot = Directory(
|
||||
'${tempDirectory.path}/installed-skills',
|
||||
);
|
||||
await seedInstalledSkillE2ERootInternal(skillsRoot);
|
||||
final skillsRoot = Directory('${tempDirectory.path}/installed-skills');
|
||||
final workspaceRoot = Directory('${tempDirectory.path}/workspace');
|
||||
await workspaceRoot.create(recursive: true);
|
||||
|
||||
final controller = await createInstalledSkillE2EControllerInternal(
|
||||
tempDirectory: tempDirectory,
|
||||
skillsRoot: skillsRoot,
|
||||
);
|
||||
await writeSkillInternal(
|
||||
skillsRoot,
|
||||
'pptx',
|
||||
skillName: 'pptx',
|
||||
description: 'Presentation creation, editing, and QA.',
|
||||
);
|
||||
await writeSkillInternal(
|
||||
skillsRoot,
|
||||
'docx',
|
||||
skillName: 'docx',
|
||||
description: 'Word document authoring and editing.',
|
||||
);
|
||||
await writeSkillInternal(
|
||||
skillsRoot,
|
||||
'xlsx',
|
||||
skillName: 'xlsx',
|
||||
description: 'Spreadsheet authoring and formula validation.',
|
||||
);
|
||||
await writeSkillInternal(
|
||||
skillsRoot,
|
||||
'pdf',
|
||||
skillName: 'pdf',
|
||||
description: 'PDF extraction, creation, and form workflows.',
|
||||
);
|
||||
|
||||
final importedSkills = controller.assistantImportedSkillsForSession(
|
||||
controller.currentSessionKey,
|
||||
);
|
||||
final importedLabels = importedSkills
|
||||
.map((item) => item.label)
|
||||
.toList(growable: false);
|
||||
final controller = await createInstalledSkillE2EControllerSimpleInternal(
|
||||
tempDirectory: tempDirectory,
|
||||
skillsRoot: skillsRoot,
|
||||
workspaceRoot: workspaceRoot,
|
||||
testCase: testCase,
|
||||
);
|
||||
|
||||
expect(
|
||||
importedLabels,
|
||||
containsAll(
|
||||
installedSkillE2ECasesInternal
|
||||
.map((item) => item.skillLabel)
|
||||
.toList(growable: false),
|
||||
),
|
||||
);
|
||||
final sendFuture = controller.sendChatMessage(
|
||||
testCase.prompt,
|
||||
selectedSkillLabels: <String>[testCase.skillKey],
|
||||
);
|
||||
await waitForConditionInternal(() => controller.sendCallCount == 1);
|
||||
|
||||
final selectedEntry = importedSkills.firstWhere(
|
||||
(item) => item.label == testCase.skillLabel,
|
||||
);
|
||||
expect(selectedEntry.source, 'custom');
|
||||
expect(selectedEntry.scope, 'user');
|
||||
expect(selectedEntry.sourcePath, endsWith('SKILL.md'));
|
||||
expect(selectedEntry.sourceLabel, isNotEmpty);
|
||||
expect(controller.lastSentMessage, contains(testCase.prompt));
|
||||
expect(controller.lastPromptInternal, contains(testCase.prompt));
|
||||
expect(
|
||||
controller.lastSelectedSkillLabelsInternal,
|
||||
equals(<String>[testCase.skillKey]),
|
||||
);
|
||||
expect(controller.lastWorkspacePathInternal, isNotEmpty);
|
||||
|
||||
await controller.toggleAssistantSkillForSession(
|
||||
controller.currentSessionKey,
|
||||
selectedEntry.key,
|
||||
);
|
||||
expect(
|
||||
controller.assistantSelectedSkillKeysForSession(
|
||||
controller.currentSessionKey,
|
||||
),
|
||||
<String>[selectedEntry.key],
|
||||
);
|
||||
controller.sendGate.complete();
|
||||
await sendFuture;
|
||||
|
||||
final sendFuture = controller.sendChatMessage(
|
||||
testCase.prompt,
|
||||
selectedSkillLabels: <String>[selectedEntry.label],
|
||||
);
|
||||
await waitForConditionInternal(() => controller.sendCallCount == 1);
|
||||
final artifactService = DesktopThreadArtifactService();
|
||||
final snapshot = await artifactService.loadSnapshot(
|
||||
workspacePath: controller.lastWorkspacePathInternal,
|
||||
workspaceKind: WorkspaceRefKind.localPath,
|
||||
);
|
||||
|
||||
expect(controller.lastPromptInternal, testCase.prompt);
|
||||
expect(controller.lastSelectedSkillLabelsInternal, <String>[
|
||||
selectedEntry.label,
|
||||
]);
|
||||
expect(
|
||||
controller.lastWorkspacePathInternal,
|
||||
controller.assistantWorkspacePathForSession(
|
||||
controller.currentSessionKey,
|
||||
),
|
||||
);
|
||||
|
||||
controller.sendGate.complete();
|
||||
await sendFuture;
|
||||
|
||||
final snapshot = await controller.loadAssistantArtifactSnapshot();
|
||||
expect(snapshot.workspaceKind, WorkspaceRefKind.localPath);
|
||||
expect(
|
||||
snapshot.fileEntries.map((item) => item.relativePath),
|
||||
contains(testCase.outputRelativePath),
|
||||
);
|
||||
expect(
|
||||
snapshot.resultEntries.map((item) => item.relativePath),
|
||||
contains(testCase.outputRelativePath),
|
||||
);
|
||||
},
|
||||
);
|
||||
expect(
|
||||
snapshot.fileEntries.map((item) => item.relativePath),
|
||||
contains(testCase.outputRelativePath),
|
||||
);
|
||||
expect(
|
||||
snapshot.resultEntries.map((item) => item.relativePath),
|
||||
contains(testCase.outputRelativePath),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
test(
|
||||
'records deferred media skill coverage explicitly',
|
||||
() {
|
||||
expect(installedSkillE2EDeferredCoverageInternal, <String>[
|
||||
test('records deferred media skill coverage explicitly', () {
|
||||
expect(
|
||||
installedSkillE2EDeferredCoverageInternal,
|
||||
equals(const <String>[
|
||||
'image-cog',
|
||||
'wan-image-video-generation-editting',
|
||||
'video-translator',
|
||||
'image-resizer',
|
||||
]);
|
||||
},
|
||||
skip:
|
||||
'Deferred until the media skill packs are installed in the test environment.',
|
||||
);
|
||||
]),
|
||||
);
|
||||
}, skip: 'Deferred until the media skill packs are installed.');
|
||||
});
|
||||
}
|
||||
|
||||
@ -176,160 +176,19 @@ Future<void> waitForConditionInternal(bool Function() predicate) async {
|
||||
}
|
||||
}
|
||||
|
||||
class InstalledSkillE2ECaseInternal {
|
||||
const InstalledSkillE2ECaseInternal({
|
||||
required this.skillKey,
|
||||
required this.skillLabel,
|
||||
required this.prompt,
|
||||
required this.outputRelativePath,
|
||||
});
|
||||
|
||||
final String skillKey;
|
||||
final String skillLabel;
|
||||
final String prompt;
|
||||
final String outputRelativePath;
|
||||
}
|
||||
|
||||
const List<InstalledSkillE2ECaseInternal> installedSkillE2ECasesInternal =
|
||||
<InstalledSkillE2ECaseInternal>[
|
||||
InstalledSkillE2ECaseInternal(
|
||||
skillKey: 'pptx',
|
||||
skillLabel: 'pptx',
|
||||
prompt: 'installed-skill harness: exercise pptx handoff',
|
||||
outputRelativePath: 'artifacts/pptx/result.md',
|
||||
),
|
||||
InstalledSkillE2ECaseInternal(
|
||||
skillKey: 'docx',
|
||||
skillLabel: 'docx',
|
||||
prompt: 'installed-skill harness: exercise docx handoff',
|
||||
outputRelativePath: 'artifacts/docx/result.md',
|
||||
),
|
||||
InstalledSkillE2ECaseInternal(
|
||||
skillKey: 'xlsx',
|
||||
skillLabel: 'xlsx',
|
||||
prompt: 'installed-skill harness: exercise xlsx handoff',
|
||||
outputRelativePath: 'artifacts/xlsx/result.md',
|
||||
),
|
||||
InstalledSkillE2ECaseInternal(
|
||||
skillKey: 'pdf',
|
||||
skillLabel: 'pdf',
|
||||
prompt: 'installed-skill harness: exercise pdf handoff',
|
||||
outputRelativePath: 'artifacts/pdf/result.md',
|
||||
),
|
||||
];
|
||||
|
||||
const List<String> installedSkillE2EDeferredCoverageInternal = <String>[
|
||||
'image-cog',
|
||||
'wan-image-video-generation-editting',
|
||||
'video-translator',
|
||||
'image-resizer',
|
||||
];
|
||||
|
||||
Future<void> seedInstalledSkillE2ERootInternal(Directory root) async {
|
||||
for (final testCase in installedSkillE2ECasesInternal) {
|
||||
await writeSkillInternal(
|
||||
root,
|
||||
testCase.skillKey,
|
||||
skillName: testCase.skillLabel,
|
||||
description: 'Installed skill ${testCase.skillLabel}',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class InstalledSkillE2EAppControllerInternal extends AppController {
|
||||
InstalledSkillE2EAppControllerInternal({
|
||||
required SecureConfigStore store,
|
||||
required this.sendGate,
|
||||
super.singleAgentSharedSkillScanRootOverrides,
|
||||
}) : super(
|
||||
store: store,
|
||||
runtimeCoordinator: RuntimeCoordinator(
|
||||
gateway: FakeGatewayRuntimeInternal(store: store),
|
||||
codex: FakeCodexRuntimeInternal(),
|
||||
),
|
||||
);
|
||||
|
||||
final Completer<void> sendGate;
|
||||
int sendCallCount = 0;
|
||||
String lastPromptInternal = '';
|
||||
List<String> lastSelectedSkillLabelsInternal = <String>[];
|
||||
String lastWorkspacePathInternal = '';
|
||||
|
||||
@override
|
||||
Future<void> sendChatMessage(
|
||||
String message, {
|
||||
String thinking = 'off',
|
||||
List<GatewayChatAttachmentPayload> attachments =
|
||||
const <GatewayChatAttachmentPayload>[],
|
||||
List<CollaborationAttachment> localAttachments =
|
||||
const <CollaborationAttachment>[],
|
||||
List<String> selectedSkillLabels = const <String>[],
|
||||
}) async {
|
||||
sendCallCount += 1;
|
||||
lastPromptInternal = message;
|
||||
lastSelectedSkillLabelsInternal = selectedSkillLabels.toList(
|
||||
growable: false,
|
||||
);
|
||||
lastWorkspacePathInternal = assistantWorkspacePathForSession(
|
||||
currentSessionKey,
|
||||
);
|
||||
if (lastWorkspacePathInternal.trim().isEmpty) {
|
||||
throw StateError('Installed-skill harness did not resolve a workspace.');
|
||||
}
|
||||
|
||||
final selectedLabel = selectedSkillLabels.isEmpty
|
||||
? 'unselected'
|
||||
: selectedSkillLabels.first;
|
||||
final artifactFile = File(
|
||||
'$lastWorkspacePathInternal/artifacts/$selectedLabel/result.md',
|
||||
);
|
||||
await artifactFile.parent.create(recursive: true);
|
||||
await artifactFile.writeAsString(
|
||||
[
|
||||
'# $selectedLabel',
|
||||
'',
|
||||
'prompt: $message',
|
||||
'thinking: $thinking',
|
||||
'selected: ${selectedSkillLabels.join(', ')}',
|
||||
'session: $currentSessionKey',
|
||||
].join('\n'),
|
||||
);
|
||||
|
||||
await sendGate.future;
|
||||
}
|
||||
}
|
||||
|
||||
Future<InstalledSkillE2EAppControllerInternal>
|
||||
createInstalledSkillE2EControllerInternal({
|
||||
required Directory tempDirectory,
|
||||
required Directory skillsRoot,
|
||||
}) async {
|
||||
SharedPreferences.setMockInitialValues(<String, Object>{});
|
||||
final controller = InstalledSkillE2EAppControllerInternal(
|
||||
store: await createStoreInternal(tempDirectory.path),
|
||||
sendGate: Completer<void>(),
|
||||
singleAgentSharedSkillScanRootOverrides: <String>[skillsRoot.path],
|
||||
);
|
||||
addTearDown(controller.dispose);
|
||||
await waitForConditionInternal(() => !controller.initializing);
|
||||
await waitForConditionInternal(
|
||||
() => controller
|
||||
.assistantImportedSkillsForSession(controller.currentSessionKey)
|
||||
.isNotEmpty,
|
||||
);
|
||||
return controller;
|
||||
}
|
||||
|
||||
class PendingSendAppControllerInternal extends AppController {
|
||||
PendingSendAppControllerInternal({
|
||||
required SecureConfigStore store,
|
||||
required this.sendGate,
|
||||
List<String>? singleAgentSharedSkillScanRootOverrides,
|
||||
}) : super(
|
||||
store: store,
|
||||
runtimeCoordinator: RuntimeCoordinator(
|
||||
gateway: FakeGatewayRuntimeInternal(store: store),
|
||||
codex: FakeCodexRuntimeInternal(),
|
||||
),
|
||||
singleAgentSharedSkillScanRootOverrides:
|
||||
singleAgentSharedSkillScanRootOverrides,
|
||||
);
|
||||
|
||||
final Completer<void> sendGate;
|
||||
@ -352,6 +211,215 @@ class PendingSendAppControllerInternal extends AppController {
|
||||
}
|
||||
}
|
||||
|
||||
class InstalledSkillE2ECaseInternal {
|
||||
const InstalledSkillE2ECaseInternal({
|
||||
required this.skillKey,
|
||||
required this.prompt,
|
||||
required this.outputRelativePath,
|
||||
required this.outputContent,
|
||||
});
|
||||
|
||||
final String skillKey;
|
||||
final String prompt;
|
||||
final String outputRelativePath;
|
||||
final String outputContent;
|
||||
}
|
||||
|
||||
const List<InstalledSkillE2ECaseInternal>
|
||||
installedSkillE2ECasesInternal = <InstalledSkillE2ECaseInternal>[
|
||||
InstalledSkillE2ECaseInternal(
|
||||
skillKey: 'pptx',
|
||||
prompt: 'Create a concise slide outline for the quarterly review.',
|
||||
outputRelativePath: 'artifacts/pptx/result.md',
|
||||
outputContent: '# pptx\n\nCaptured slide outline for the quarterly review.',
|
||||
),
|
||||
InstalledSkillE2ECaseInternal(
|
||||
skillKey: 'docx',
|
||||
prompt: 'Draft a short policy note with headings and bullets.',
|
||||
outputRelativePath: 'artifacts/docx/result.md',
|
||||
outputContent: '# docx\n\nCaptured policy note with headings and bullets.',
|
||||
),
|
||||
InstalledSkillE2ECaseInternal(
|
||||
skillKey: 'xlsx',
|
||||
prompt: 'Prepare a tiny table with one formula and one formatted cell.',
|
||||
outputRelativePath: 'artifacts/xlsx/result.md',
|
||||
outputContent: '# xlsx\n\nCaptured spreadsheet result with formula notes.',
|
||||
),
|
||||
InstalledSkillE2ECaseInternal(
|
||||
skillKey: 'pdf',
|
||||
prompt: 'Summarize a reference PDF and keep the output deterministic.',
|
||||
outputRelativePath: 'artifacts/pdf/result.md',
|
||||
outputContent: '# pdf\n\nCaptured PDF summary output.',
|
||||
),
|
||||
];
|
||||
|
||||
const List<String> installedSkillE2EDeferredCoverageInternal = <String>[
|
||||
'image-cog',
|
||||
'wan-image-video-generation-editting',
|
||||
'video-translator',
|
||||
'image-resizer',
|
||||
];
|
||||
|
||||
class InstalledSkillE2EAppControllerInternal
|
||||
extends PendingSendAppControllerInternal {
|
||||
InstalledSkillE2EAppControllerInternal({
|
||||
required super.store,
|
||||
required super.sendGate,
|
||||
required this.outputRelativePath,
|
||||
required this.outputContent,
|
||||
required this.importedSkill,
|
||||
super.singleAgentSharedSkillScanRootOverrides,
|
||||
this.sessionKey = 'installed-skill-session',
|
||||
});
|
||||
|
||||
final String outputRelativePath;
|
||||
final String outputContent;
|
||||
final AssistantThreadSkillEntry importedSkill;
|
||||
final String sessionKey;
|
||||
String lastPromptInternal = '';
|
||||
List<String> lastSelectedSkillLabelsInternal = const <String>[];
|
||||
String lastWorkspacePathInternal = '';
|
||||
|
||||
@override
|
||||
Future<void> sendChatMessage(
|
||||
String message, {
|
||||
String thinking = 'off',
|
||||
List<GatewayChatAttachmentPayload> attachments =
|
||||
const <GatewayChatAttachmentPayload>[],
|
||||
List<CollaborationAttachment> localAttachments =
|
||||
const <CollaborationAttachment>[],
|
||||
List<String> selectedSkillLabels = const <String>[],
|
||||
}) async {
|
||||
lastPromptInternal = message;
|
||||
lastSelectedSkillLabelsInternal = List<String>.unmodifiable(
|
||||
selectedSkillLabels,
|
||||
);
|
||||
lastWorkspacePathInternal = assistantWorkspacePathForSession(
|
||||
sessionKey,
|
||||
);
|
||||
final workspacePath = lastWorkspacePathInternal.trim();
|
||||
if (workspacePath.isNotEmpty) {
|
||||
final outputFile = File('$workspacePath/$outputRelativePath');
|
||||
await outputFile.parent.create(recursive: true);
|
||||
await outputFile.writeAsString(outputContent, flush: true);
|
||||
}
|
||||
await super.sendChatMessage(
|
||||
message,
|
||||
thinking: thinking,
|
||||
attachments: attachments,
|
||||
localAttachments: localAttachments,
|
||||
selectedSkillLabels: selectedSkillLabels,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
String get currentSessionKey => sessionKey;
|
||||
}
|
||||
|
||||
Future<InstalledSkillE2EAppControllerInternal>
|
||||
createInstalledSkillE2EControllerInternal(
|
||||
WidgetTester tester, {
|
||||
required Directory tempDirectory,
|
||||
required Directory skillsRoot,
|
||||
required Directory workspaceRoot,
|
||||
required InstalledSkillE2ECaseInternal testCase,
|
||||
}) async {
|
||||
SharedPreferences.setMockInitialValues(<String, Object>{});
|
||||
print('installed-skill ${testCase.skillKey}: helper creating store');
|
||||
final store = SecureConfigStore(
|
||||
enableSecureStorage: false,
|
||||
databasePathResolver: () async => '${tempDirectory.path}/settings.db',
|
||||
fallbackDirectoryPathResolver: () async => tempDirectory.path,
|
||||
defaultSupportDirectoryPathResolver: () async => tempDirectory.path,
|
||||
);
|
||||
await store.initialize();
|
||||
await store.saveSettingsSnapshot(
|
||||
singleAgentTestSettingsInternal(workspacePath: workspaceRoot.path).copyWith(
|
||||
assistantExecutionTarget: AssistantExecutionTarget.singleAgent,
|
||||
multiAgent: MultiAgentConfig.defaults().copyWith(enabled: false),
|
||||
),
|
||||
);
|
||||
print('installed-skill ${testCase.skillKey}: helper creating controller');
|
||||
|
||||
final controller = InstalledSkillE2EAppControllerInternal(
|
||||
store: store,
|
||||
sendGate: Completer<void>(),
|
||||
outputRelativePath: testCase.outputRelativePath,
|
||||
outputContent: testCase.outputContent,
|
||||
importedSkill: AssistantThreadSkillEntry(
|
||||
key: testCase.skillKey,
|
||||
label: testCase.skillKey,
|
||||
description: 'Installed skill under test',
|
||||
sourcePath: '${skillsRoot.path}/${testCase.skillKey}',
|
||||
sourceLabel: testCase.skillKey,
|
||||
),
|
||||
singleAgentSharedSkillScanRootOverrides: <String>[skillsRoot.path],
|
||||
);
|
||||
print('installed-skill ${testCase.skillKey}: helper controller created');
|
||||
addTearDown(controller.dispose);
|
||||
print('installed-skill ${testCase.skillKey}: helper pumping once');
|
||||
await tester.pump(const Duration(milliseconds: 100));
|
||||
print('installed-skill ${testCase.skillKey}: helper pumped once');
|
||||
final stopwatch = Stopwatch()..start();
|
||||
while (controller.initializing) {
|
||||
print(
|
||||
'installed-skill ${testCase.skillKey}: helper waiting ${stopwatch.elapsedMilliseconds}ms',
|
||||
);
|
||||
if (stopwatch.elapsed > const Duration(seconds: 10)) {
|
||||
fail('controller did not finish initializing before timeout');
|
||||
}
|
||||
await tester.pump(const Duration(milliseconds: 20));
|
||||
}
|
||||
controller.upsertTaskThreadInternal(
|
||||
controller.currentSessionKey,
|
||||
importedSkills: <AssistantThreadSkillEntry>[controller.importedSkill],
|
||||
selectedSkillKeys: <String>[controller.importedSkill.key],
|
||||
);
|
||||
print('installed-skill ${testCase.skillKey}: helper initialized');
|
||||
return controller;
|
||||
}
|
||||
|
||||
Future<InstalledSkillE2EAppControllerInternal>
|
||||
createInstalledSkillE2EControllerSimpleInternal({
|
||||
required Directory tempDirectory,
|
||||
required Directory skillsRoot,
|
||||
required Directory workspaceRoot,
|
||||
required InstalledSkillE2ECaseInternal testCase,
|
||||
}) async {
|
||||
SharedPreferences.setMockInitialValues(<String, Object>{});
|
||||
final store = SecureConfigStore(
|
||||
enableSecureStorage: false,
|
||||
databasePathResolver: () async => '${tempDirectory.path}/settings.db',
|
||||
fallbackDirectoryPathResolver: () async => tempDirectory.path,
|
||||
defaultSupportDirectoryPathResolver: () async => tempDirectory.path,
|
||||
);
|
||||
await store.initialize();
|
||||
await store.saveSettingsSnapshot(
|
||||
singleAgentTestSettingsInternal(workspacePath: workspaceRoot.path).copyWith(
|
||||
assistantExecutionTarget: AssistantExecutionTarget.singleAgent,
|
||||
multiAgent: MultiAgentConfig.defaults().copyWith(enabled: false),
|
||||
),
|
||||
);
|
||||
|
||||
final controller = InstalledSkillE2EAppControllerInternal(
|
||||
store: store,
|
||||
sendGate: Completer<void>(),
|
||||
outputRelativePath: testCase.outputRelativePath,
|
||||
outputContent: testCase.outputContent,
|
||||
importedSkill: AssistantThreadSkillEntry(
|
||||
key: testCase.skillKey,
|
||||
label: testCase.skillKey,
|
||||
description: 'Installed skill under test',
|
||||
sourcePath: '${skillsRoot.path}/${testCase.skillKey}',
|
||||
sourceLabel: testCase.skillKey,
|
||||
),
|
||||
singleAgentSharedSkillScanRootOverrides: <String>[skillsRoot.path],
|
||||
);
|
||||
addTearDown(controller.dispose);
|
||||
await waitForConditionInternal(() => !controller.initializing);
|
||||
return controller;
|
||||
}
|
||||
|
||||
class CaptureSendAppControllerInternal extends AppController {
|
||||
CaptureSendAppControllerInternal({
|
||||
required SecureConfigStore store,
|
||||
|
||||
Loading…
Reference in New Issue
Block a user