feat(learn): Learn's agent, an MCP server suggesting in shared studysets
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>
This commit is contained in:
Tizian 「ティツィアーン」 2026-11-11 22:06:29 +01:00
commit 54e212a51c
Signed by: raisondetredev
GPG key ID: 07F6FCF405488B64
100 changed files with 2925 additions and 314 deletions

View file

@ -12,19 +12,15 @@ import 'package:halkyon_learn_core/feature/note/model/note_content.dart';
import 'package:halkyon_learn_core/feature/studyset/data/studyset_payload.dart';
import 'package:halkyon_learn_core/feature/studyset/model/studyset_content.dart';
import 'package:halkyon_learn_core/feature/tag/data/tag_repository.dart';
import 'package:halkyon_learn_core/testing.dart';
import 'package:halkyon_learn_flutter/src/core/router/route_paths.dart';
import 'package:halkyon_learn_flutter/src/feature/pomodoro/model/pomodoro_profile.dart';
import 'package:halkyon_learn_flutter/src/feature/settings/data/settings_repository.dart';
import 'package:halkyon_ui/halkyon_ui.dart';
import 'package:material_ui/material_ui.dart';
import '../support/fake_card_gateway.dart';
import '../support/fake_discussion_gateway.dart';
import '../support/fake_learning_path_gateway.dart';
import '../support/fake_note_gateway.dart';
import '../support/fake_settings_gateway.dart';
import '../support/fake_studyset_gateway.dart';
import '../support/fake_tag_gateway.dart';
import '../support/pump_app.dart';
import '../support/test_dependencies.dart';

View file

@ -5,10 +5,9 @@ import 'package:flutter_test/flutter_test.dart';
import 'package:halkyon_app/testing.dart'
show FakeFileServerGateway, MemoryFileCache, sha256Of;
import 'package:halkyon_learn_core/core/asset/data/asset_repository.dart';
import 'package:halkyon_learn_core/testing.dart';
import 'package:halkyon_learn_flutter/src/core/asset/bloc/sound_bloc.dart';
import '../../../support/fake_asset_gateway.dart';
import '../../../support/fake_asset_sealing.dart';
import '../../../support/fake_sound_player.dart';
void main() {

View file

@ -8,9 +8,7 @@ import 'package:halkyon_app/testing.dart'
import 'package:halkyon_learn_core/core/asset/data/asset_payload.dart';
import 'package:halkyon_learn_core/core/asset/data/asset_repository.dart';
import 'package:halkyon_learn_core/core/asset/model/stored_file.dart';
import '../../../support/fake_asset_gateway.dart';
import '../../../support/fake_asset_sealing.dart';
import 'package:halkyon_learn_core/testing.dart';
void main() {
late FakeAssetGateway rows;

View file

@ -6,9 +6,7 @@ import 'package:halkyon_app/testing.dart'
show FakeFileServerGateway, MemoryFileCache, sha256Of;
import 'package:halkyon_learn_core/core/asset/data/asset_payload.dart';
import 'package:halkyon_learn_core/core/asset/data/asset_repository.dart';
import '../../../support/fake_asset_gateway.dart';
import '../../../support/fake_asset_sealing.dart';
import 'package:halkyon_learn_core/testing.dart';
/// A private studyset's files (ADR 0019, D5): sealed with its tree's key for
/// their row before they are hashed, uploaded and kept.

View file

@ -7,14 +7,12 @@ import 'package:halkyon_app/testing.dart';
import 'package:halkyon_editor/halkyon_editor.dart';
import 'package:halkyon_learn_core/feature/studyset/data/studyset_payload.dart';
import 'package:halkyon_learn_core/feature/studyset/model/studyset_content.dart';
import 'package:halkyon_learn_core/testing.dart';
import 'package:halkyon_learn_flutter/src/core/editor/learn_content.dart';
import 'package:halkyon_learn_flutter/src/core/l10n/app_languages.dart';
import 'package:halkyon_learn_flutter/src/core/router/route_paths.dart';
import 'package:material_ui/material_ui.dart';
import '../../support/fake_asset_gateway.dart';
import '../../support/fake_note_gateway.dart';
import '../../support/fake_studyset_gateway.dart';
import '../../support/pump_app.dart';
import '../../support/test_dependencies.dart';

View file

@ -12,16 +12,13 @@ import 'package:halkyon_learn_core/feature/card/data/card_repository.dart';
import 'package:halkyon_learn_core/feature/card/model/card_content.dart';
import 'package:halkyon_learn_core/feature/studyset/data/studyset_payload.dart';
import 'package:halkyon_learn_core/feature/studyset/model/studyset_content.dart';
import 'package:halkyon_learn_core/testing.dart';
import 'package:halkyon_learn_flutter/src/core/asset/widget/stored_file_image.dart';
import 'package:halkyon_learn_flutter/src/core/editor/file_blocks.dart';
import 'package:halkyon_learn_flutter/src/core/router/route_paths.dart';
import 'package:material_ui/material_ui.dart';
import '../../support/fake_asset_gateway.dart';
import '../../support/fake_asset_sealing.dart';
import '../../support/fake_card_gateway.dart';
import '../../support/fake_sound_player.dart';
import '../../support/fake_studyset_gateway.dart';
import '../../support/pump_app.dart';
import '../../support/test_dependencies.dart';

View file

@ -4,6 +4,7 @@ import 'package:halkyon_learn_core/feature/note/data/note_repository.dart';
import 'package:halkyon_learn_core/feature/note/model/note_content.dart';
import 'package:halkyon_learn_core/feature/studyset/data/studyset_payload.dart';
import 'package:halkyon_learn_core/feature/studyset/model/studyset_content.dart';
import 'package:halkyon_learn_core/testing.dart';
import 'package:halkyon_learn_flutter/src/core/device_settings/model/device_settings.dart';
import 'package:halkyon_learn_flutter/src/core/grammar/grammar_match.dart';
import 'package:halkyon_learn_flutter/src/core/grammar/grammar_overlay.dart';
@ -13,9 +14,7 @@ import 'package:halkyon_learn_flutter/src/feature/settings/model/person_settings
import 'package:material_ui/material_ui.dart';
import '../../support/fake_grammar_gateway.dart';
import '../../support/fake_note_gateway.dart';
import '../../support/fake_settings_gateway.dart';
import '../../support/fake_studyset_gateway.dart';
import '../../support/pump_app.dart';
import '../../support/test_dependencies.dart';

View file

@ -14,10 +14,10 @@ import 'package:halkyon_learn_core/core/privacy/data/privacy_ledger.dart';
import 'package:halkyon_learn_core/core/privacy/data/sync_content_key_gateway.dart';
import 'package:halkyon_learn_core/core/privacy/data/tree_files.dart';
import 'package:halkyon_learn_core/feature/card/data/sync_card_gateway.dart';
import 'package:halkyon_learn_core/feature/learning_path/data/sync_learning_path_gateway.dart';
import 'package:halkyon_learn_core/feature/note/data/sync_note_gateway.dart';
import 'package:halkyon_learn_core/feature/studyset/data/sync_studyset_gateway.dart';
import 'package:halkyon_learn_core/feature/tag/data/sync_tag_gateway.dart';
import 'package:halkyon_learn_flutter/src/feature/learning_path/data/sync_learning_path_gateway.dart';
import 'package:halkyon_learn_flutter/src/feature/learning_path/data/sync_path_answer_gateway.dart';
import 'package:halkyon_learn_flutter/src/feature/pdf_source/data/sync_pdf_source_gateway.dart';
import 'package:halkyon_learn_flutter/src/feature/publish/data/publish_repository.dart';

View file

@ -16,9 +16,9 @@ import 'package:halkyon_learn_core/core/privacy/data/sync_content_key_gateway.da
import 'package:halkyon_learn_core/core/privacy/data/tree_files.dart';
import 'package:halkyon_learn_core/core/privacy/model/unsigned_edit.dart';
import 'package:halkyon_learn_core/feature/card/data/sync_card_gateway.dart';
import 'package:halkyon_learn_core/feature/learning_path/data/sync_learning_path_gateway.dart';
import 'package:halkyon_learn_core/feature/studyset/data/sync_studyset_gateway.dart';
import 'package:halkyon_learn_core/feature/tag/data/sync_tag_gateway.dart';
import 'package:halkyon_learn_flutter/src/feature/learning_path/data/sync_learning_path_gateway.dart';
import 'package:halkyon_learn_flutter/src/feature/learning_path/data/sync_path_answer_gateway.dart';
import 'package:halkyon_learn_flutter/src/feature/settings/data/sync_settings_gateway.dart';
import 'package:serverpod_offline_sync_client/offline_sync.dart';

View file

@ -5,6 +5,7 @@ import 'package:halkyon_learn_core/feature/card/data/card_repository.dart';
import 'package:halkyon_learn_core/feature/card/model/card_content.dart';
import 'package:halkyon_learn_core/feature/studyset/data/studyset_payload.dart';
import 'package:halkyon_learn_core/feature/studyset/model/studyset_content.dart';
import 'package:halkyon_learn_core/testing.dart';
import 'package:halkyon_learn_flutter/src/core/router/route_paths.dart';
import 'package:halkyon_learn_flutter/src/feature/home/widget/activity_heatmap.dart';
import 'package:halkyon_learn_flutter/src/feature/home/widget/home_screen.dart';
@ -13,8 +14,6 @@ import 'package:halkyon_learn_flutter/src/feature/studyset/widget/studyset_scree
import 'package:halkyon_ui/halkyon_ui.dart';
import 'package:material_ui/material_ui.dart';
import '../../support/fake_card_gateway.dart';
import '../../support/fake_studyset_gateway.dart';
import '../../support/memory_device_settings_store.dart';
import '../../support/pump_app.dart';
import '../../support/test_dependencies.dart';

View file

@ -4,12 +4,11 @@ import 'package:halkyon_learn_core/feature/card/data/card_repository.dart';
import 'package:halkyon_learn_core/feature/card/model/card_content.dart';
import 'package:halkyon_learn_core/feature/studyset/data/studyset_payload.dart';
import 'package:halkyon_learn_core/feature/studyset/model/studyset_content.dart';
import 'package:halkyon_learn_core/testing.dart';
import 'package:halkyon_learn_flutter/src/core/router/route_paths.dart';
import 'package:halkyon_ui/halkyon_ui.dart';
import 'package:material_ui/material_ui.dart';
import '../../support/fake_card_gateway.dart';
import '../../support/fake_studyset_gateway.dart';
import '../../support/pump_app.dart';
import '../../support/test_dependencies.dart';

View file

@ -13,6 +13,7 @@ import 'package:halkyon_learn_core/feature/note/data/note_repository.dart';
import 'package:halkyon_learn_core/feature/studyset/data/studyset_repository.dart';
import 'package:halkyon_learn_core/feature/studyset/model/studyset_content.dart';
import 'package:halkyon_learn_core/feature/tag/data/tag_repository.dart';
import 'package:halkyon_learn_core/testing.dart';
import 'package:halkyon_learn_flutter/src/core/work/work_progress.dart';
import 'package:halkyon_learn_flutter/src/feature/anki/data/anki_importer.dart';
import 'package:halkyon_learn_flutter/src/feature/anki/data/anki_package.dart';
@ -22,15 +23,8 @@ import 'package:halkyon_learn_flutter/src/feature/study/model/rating.dart';
import 'package:halkyon_learn_flutter/src/feature/study/model/review.dart';
import 'package:halkyon_learn_flutter/src/feature/studyset/data/studyset_deletions.dart';
import '../../../support/fake_asset_gateway.dart';
import '../../../support/fake_asset_sealing.dart';
import '../../../support/fake_card_gateway.dart';
import '../../../support/fake_learning_path_gateway.dart';
import '../../../support/fake_note_gateway.dart';
import '../../../support/fake_pdf_source_gateway.dart';
import '../../../support/fake_review_gateway.dart';
import '../../../support/fake_studyset_gateway.dart';
import '../../../support/fake_tag_gateway.dart';
import 'anki_package_test.dart' show fixture;
void main() {

View file

@ -10,14 +10,12 @@ import 'package:halkyon_learn_core/feature/note/model/note_content.dart';
import 'package:halkyon_learn_core/feature/studyset/data/studyset_payload.dart';
import 'package:halkyon_learn_core/feature/studyset/data/studyset_repository.dart';
import 'package:halkyon_learn_core/feature/studyset/model/studyset_content.dart';
import 'package:halkyon_learn_core/testing.dart';
import 'package:halkyon_learn_flutter/src/core/router/route_paths.dart';
import 'package:halkyon_learn_flutter/src/core/widget/work_progress_view.dart';
import 'package:halkyon_learn_flutter/src/feature/studyset/bloc/content_tab_cubit.dart';
import 'package:material_ui/material_ui.dart';
import '../../../support/fake_card_gateway.dart';
import '../../../support/fake_note_gateway.dart';
import '../../../support/fake_studyset_gateway.dart';
import '../../../support/pump_app.dart';
import '../../../support/test_dependencies.dart';
import '../data/anki_package_test.dart' show fixture;

View file

@ -22,6 +22,7 @@ import 'package:halkyon_learn_core/feature/studyset/model/exam_plan.dart';
import 'package:halkyon_learn_core/feature/studyset/model/studyset_content.dart';
import 'package:halkyon_learn_core/feature/studyset/model/studyset_tree.dart';
import 'package:halkyon_learn_core/feature/tag/data/tag_repository.dart';
import 'package:halkyon_learn_core/testing.dart';
import 'package:halkyon_learn_flutter/src/core/work/work_progress.dart';
import 'package:halkyon_learn_flutter/src/feature/archive/data/archive_exporter.dart';
import 'package:halkyon_learn_flutter/src/feature/archive/data/archive_importer.dart';
@ -40,18 +41,11 @@ import 'package:halkyon_learn_flutter/src/feature/study/model/review.dart';
import 'package:halkyon_learn_flutter/src/feature/studyset/data/studyset_deletions.dart';
import 'package:halkyon_ui/halkyon_ui.dart';
import '../../support/fake_asset_gateway.dart';
import '../../support/fake_asset_sealing.dart';
import '../../support/fake_card_gateway.dart';
import '../../support/fake_device_dictionaries.dart';
import '../../support/fake_dictionary_gateway.dart';
import '../../support/fake_learning_path_gateway.dart';
import '../../support/fake_note_gateway.dart';
import '../../support/fake_path_answer_gateway.dart';
import '../../support/fake_pdf_source_gateway.dart';
import '../../support/fake_review_gateway.dart';
import '../../support/fake_studyset_gateway.dart';
import '../../support/fake_tag_gateway.dart';
/// One person's repositories, on fake gateways of their own.
final class _Person() {

View file

@ -22,6 +22,7 @@ import 'package:halkyon_learn_core/feature/studyset/data/studyset_payload.dart';
import 'package:halkyon_learn_core/feature/studyset/data/studyset_repository.dart';
import 'package:halkyon_learn_core/feature/studyset/model/studyset_content.dart';
import 'package:halkyon_learn_core/feature/tag/data/tag_repository.dart';
import 'package:halkyon_learn_core/testing.dart';
import 'package:halkyon_learn_flutter/src/core/work/work_progress.dart';
import 'package:halkyon_learn_flutter/src/feature/archive/data/archive_importer.dart';
import 'package:halkyon_learn_flutter/src/feature/archive/data/archive_reader.dart';
@ -33,18 +34,11 @@ import 'package:halkyon_learn_flutter/src/feature/study/data/review_repository.d
import 'package:halkyon_learn_flutter/src/feature/study/model/rating.dart';
import 'package:halkyon_learn_flutter/src/feature/studyset/data/studyset_deletions.dart';
import '../../support/fake_asset_gateway.dart';
import '../../support/fake_asset_sealing.dart';
import '../../support/fake_card_gateway.dart';
import '../../support/fake_device_dictionaries.dart';
import '../../support/fake_dictionary_gateway.dart';
import '../../support/fake_learning_path_gateway.dart';
import '../../support/fake_note_gateway.dart';
import '../../support/fake_path_answer_gateway.dart';
import '../../support/fake_pdf_source_gateway.dart';
import '../../support/fake_review_gateway.dart';
import '../../support/fake_studyset_gateway.dart';
import '../../support/fake_tag_gateway.dart';
/// The JSON a synced [payload] holds.
Map<String, Object?> json(ByteData payload) =>

View file

@ -3,14 +3,13 @@ import 'dart:async';
import 'package:flutter_test/flutter_test.dart';
import 'package:halkyon_app/testing.dart';
import 'package:halkyon_learn_core/feature/studyset/data/studyset_repository.dart';
import 'package:halkyon_learn_core/testing.dart';
import 'package:halkyon_learn_flutter/src/core/router/route_paths.dart';
import 'package:halkyon_learn_flutter/src/core/widget/work_progress_view.dart';
import 'package:halkyon_learn_flutter/src/feature/studyset/widget/studyset_screen.dart';
import 'package:material_ui/material_ui.dart';
import '../../support/fake_card_gateway.dart';
import '../../support/fake_dictionary_gateway.dart';
import '../../support/fake_studyset_gateway.dart';
import '../../support/pump_app.dart';
import '../../support/test_dependencies.dart';
import 'archive_test.dart' show japanese, zip;

View file

@ -6,6 +6,7 @@ import 'package:halkyon_learn_core/feature/card/data/card_repository.dart';
import 'package:halkyon_learn_core/feature/card/model/card_content.dart';
import 'package:halkyon_learn_core/feature/studyset/data/studyset_payload.dart';
import 'package:halkyon_learn_core/feature/studyset/model/studyset_content.dart';
import 'package:halkyon_learn_core/testing.dart';
import 'package:halkyon_learn_flutter/src/core/device_settings/model/device_settings.dart';
import 'package:halkyon_learn_flutter/src/core/router/route_paths.dart';
import 'package:halkyon_learn_flutter/src/feature/assistant/data/web_search.dart';
@ -17,8 +18,6 @@ import 'package:halkyon_learn_flutter/src/feature/study/widget/rating_row.dart';
import 'package:lucide_icons_flutter/lucide_icons.dart';
import 'package:material_ui/material_ui.dart';
import '../../support/fake_card_gateway.dart';
import '../../support/fake_studyset_gateway.dart';
import '../../support/memory_device_settings_store.dart';
import '../../support/pump_app.dart';
import '../../support/test_dependencies.dart';

View file

@ -3,13 +3,12 @@ import 'package:halkyon_app/halkyon_app.dart';
import 'package:halkyon_app/testing.dart';
import 'package:halkyon_learn_core/feature/card/data/card_payload.dart';
import 'package:halkyon_learn_core/feature/card/data/card_repository.dart';
import 'package:halkyon_learn_core/testing.dart';
import 'package:halkyon_learn_flutter/src/feature/assistant/bloc/card_proposals_cubit.dart';
import 'package:halkyon_learn_flutter/src/feature/assistant/model/assistant_prompt.dart';
import 'package:halkyon_learn_flutter/src/feature/assistant/model/card_proposals.dart';
import 'package:halkyon_llm/halkyon_llm.dart';
import '../../../support/fake_card_gateway.dart';
// Proposing cards from a text (ADR 0021, D55): one question with the one
// tool, the cards it proposes all chosen at first, changed or left out by
// the person, and only those chosen added, tied to the page they came from.

View file

@ -12,19 +12,15 @@ import 'package:halkyon_learn_core/feature/note/data/note_repository.dart';
import 'package:halkyon_learn_core/feature/note/model/note_content.dart';
import 'package:halkyon_learn_core/feature/studyset/data/studyset_payload.dart';
import 'package:halkyon_learn_core/feature/studyset/model/studyset_content.dart';
import 'package:halkyon_learn_core/testing.dart';
import 'package:halkyon_learn_flutter/src/core/router/route_paths.dart';
import 'package:halkyon_learn_flutter/src/feature/assistant/bloc/card_proposals_cubit.dart';
import 'package:halkyon_learn_flutter/src/feature/pdf_source/data/pdf_source_repository.dart';
import 'package:halkyon_llm/halkyon_llm.dart';
import 'package:material_ui/material_ui.dart' hide Card;
import '../../support/fake_asset_gateway.dart';
import '../../support/fake_asset_sealing.dart';
import '../../support/fake_card_gateway.dart';
import '../../support/fake_note_gateway.dart';
import '../../support/fake_pdf_engine.dart';
import '../../support/fake_pdf_source_gateway.dart';
import '../../support/fake_studyset_gateway.dart';
import '../../support/pump_app.dart';
import '../../support/test_dependencies.dart';

View file

@ -10,6 +10,7 @@ import 'package:halkyon_learn_core/feature/card/data/card_repository.dart';
import 'package:halkyon_learn_core/feature/card/model/card_content.dart';
import 'package:halkyon_learn_core/feature/studyset/data/studyset_payload.dart';
import 'package:halkyon_learn_core/feature/studyset/model/studyset_content.dart';
import 'package:halkyon_learn_core/testing.dart';
import 'package:halkyon_learn_flutter/src/core/device_settings/model/device_settings.dart';
import 'package:halkyon_learn_flutter/src/core/grammar/grammar_match.dart';
import 'package:halkyon_learn_flutter/src/core/router/route_paths.dart';
@ -27,14 +28,10 @@ import 'package:halkyon_llm/halkyon_llm.dart';
import 'package:lucide_icons_flutter/lucide_icons.dart';
import 'package:material_ui/material_ui.dart';
import '../../support/fake_asset_gateway.dart';
import '../../support/fake_asset_sealing.dart';
import '../../support/fake_card_gateway.dart';
import '../../support/fake_conversation_gateway.dart';
import '../../support/fake_grammar_gateway.dart';
import '../../support/fake_photo_picker.dart';
import '../../support/fake_settings_gateway.dart';
import '../../support/fake_studyset_gateway.dart';
import '../../support/memory_device_settings_store.dart';
import '../../support/pump_app.dart';
import '../../support/test_dependencies.dart';

Some files were not shown because too many files have changed in this diff Show more