Some checks failed
CI / check (push) Failing after 1m56s
Halkyon's ADR 0044, step 6c (D7, D49 to D52). apps/learn/halkyon_learn_agent is one executable, `halkyon-agent --instance URL sign-in | sync | mcp`: - sign-in keeps the token read from stdin, signs in with it, makes the agent's key pair on its first sign-in, keeps it before setting the public halves on the register (agents.setKeys), and syncs; - it opens its data as the app's _openPersonData does, on halkyon_learn_core and halkyon_content: its own SQLite database, the sync's space router, its keys and those of the spaces shared with it, and the repositories of studysets, cards, notes, paths, files and feedback; - mcp serves ten tools over stdio (dart_mcp): list, read and search the studysets shared with it, suggest a change or a new card or note, report, list a studyset's suggestions and reply. Each syncs first and a suggestion again at once; a refusal is the tool's error; - its files live in $XDG_CONFIG_HOME/halkyon-agent/<host>, 0700 and 0600, refused when others may read them (D50); - tool/install.sh builds it with `dart build cli` (libsodium and SQLite bundled) and copies it to its user on the host, the build before kept (D52). The synced gateways of learning paths and files, and the fakes of the core's gateways, move into halkyon_learn_core (its testing.dart), which the app's tests and the agent's share. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
60 lines
1.5 KiB
Dart
60 lines
1.5 KiB
Dart
import 'dart:async';
|
|
|
|
import 'package:halkyon_learn_agent/src/agent_sync.dart';
|
|
import 'package:test/test.dart';
|
|
|
|
/// The agent's sync (Halkyon's ADR 0044, D7), the `SyncRequests` its
|
|
/// gateways ask: a burst of requests syncs once after a pause, a tool's
|
|
/// sync runs at once and says whether it went, and what a sync merged is
|
|
/// heard.
|
|
void main() {
|
|
late List<String> runs;
|
|
late Exception? fails;
|
|
late AgentSync sync;
|
|
|
|
setUp(() {
|
|
runs = [];
|
|
fails = null;
|
|
sync = AgentSync(({required onMerge}) async {
|
|
runs.add('sync');
|
|
if (fails case final error?) {
|
|
throw error;
|
|
}
|
|
onMerge();
|
|
}, debounce: const Duration(milliseconds: 50));
|
|
addTearDown(sync.dispose);
|
|
});
|
|
|
|
test('requests in a burst sync once, after the pause', () async {
|
|
sync
|
|
..requestSync()
|
|
..requestSync()
|
|
..requestSync();
|
|
expect(runs, isEmpty);
|
|
|
|
await Future<void>.delayed(const Duration(milliseconds: 150));
|
|
|
|
expect(runs, ['sync']);
|
|
});
|
|
|
|
test('a sync now runs at once, and what it merged is heard', () async {
|
|
final merged = sync.merges.first;
|
|
final synced = sync.synced.first;
|
|
|
|
await sync.syncNow();
|
|
|
|
expect(runs, ['sync']);
|
|
await merged;
|
|
await synced;
|
|
});
|
|
|
|
test('a sync now that fails says so; the next that goes through '
|
|
"doesn't", () async {
|
|
fails = Exception('offline');
|
|
await expectLater(sync.syncNow(), throwsA(isA<Exception>()));
|
|
|
|
fails = null;
|
|
await sync.syncNow();
|
|
expect(runs, ['sync', 'sync']);
|
|
});
|
|
}
|