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
78 changed files with 2925 additions and 314 deletions

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';

View file

@ -6,11 +6,10 @@ import 'package:flutter_test/flutter_test.dart';
import 'package:halkyon_app/testing.dart'
show FakeFileServerGateway, MemoryFileCache;
import 'package:halkyon_learn_core/core/asset/data/asset_repository.dart';
import 'package:halkyon_learn_core/testing.dart';
import 'package:halkyon_learn_flutter/src/feature/assistant/data/assistant_files.dart';
import 'package:halkyon_learn_flutter/src/feature/pdf_source/model/pdf_source.dart';
import '../../../support/fake_asset_gateway.dart';
import '../../../support/fake_asset_sealing.dart';
import '../../../support/fake_pdf_engine.dart';
// What a model is sent of the person's files (ADR 0021, D12): images every

View file

@ -4,11 +4,9 @@ import 'package:halkyon_learn_core/feature/card/data/card_deletions.dart';
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/tag/data/tag_repository.dart';
import 'package:halkyon_learn_core/testing.dart';
import 'package:halkyon_learn_flutter/src/feature/card/bloc/card_list_bloc.dart';
import '../../../support/fake_card_gateway.dart';
import '../../../support/fake_tag_gateway.dart';
RichContent text(String text) => RichContent({
'document': {'type': 'page', 'note': text},
});

View file

@ -6,14 +6,13 @@ import 'package:halkyon_learn_core/feature/card/data/card_deletions.dart';
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/tag/data/tag_repository.dart';
import 'package:halkyon_learn_core/testing.dart';
import 'package:halkyon_learn_flutter/src/feature/card/bloc/card_browser_bloc.dart';
import 'package:halkyon_learn_flutter/src/feature/card/model/card_browser.dart';
import 'package:halkyon_learn_flutter/src/feature/study/data/review_repository.dart';
import 'package:halkyon_learn_flutter/src/feature/study/model/rating.dart';
import '../../../support/fake_card_gateway.dart';
import '../../../support/fake_review_gateway.dart';
import '../../../support/fake_tag_gateway.dart';
CardContent word(String front, String reading, String meaning) => CardContent(
type: CardType.vocabulary,

View file

@ -4,11 +4,9 @@ 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/feature/card/model/card_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/feature/card/bloc/card_composer_bloc.dart';
import '../../../support/fake_card_gateway.dart';
import '../../../support/fake_tag_gateway.dart';
RichContent text(String text) => RichContent({
'document': {
'type': 'page',

View file

@ -10,8 +10,7 @@ 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/feature/card/model/card.dart';
import 'package:halkyon_learn_core/feature/card/model/card_content.dart';
import '../../../support/fake_card_gateway.dart';
import 'package:halkyon_learn_core/testing.dart';
/// A [FakeCardGateway] counting how often it reads a studyset's cards.
class _CountingCardGateway() extends FakeCardGateway {

View file

@ -4,14 +4,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/router/route_paths.dart';
import 'package:halkyon_learn_flutter/src/feature/card/widget/composer/card_composer_screen.dart';
import 'package:halkyon_ui/halkyon_ui.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';

View file

@ -8,6 +8,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/editor/learn_content.dart';
import 'package:halkyon_learn_flutter/src/core/router/route_paths.dart';
import 'package:halkyon_learn_flutter/src/feature/card/widget/composer/card_face.dart';
@ -16,9 +17,7 @@ import 'package:halkyon_learn_flutter/src/feature/geo_map/widget/region_map.dart
import 'package:halkyon_ui/halkyon_ui.dart';
import 'package:material_ui/material_ui.dart' hide Card;
import '../../../support/fake_card_gateway.dart';
import '../../../support/fake_grammar_gateway.dart';
import '../../../support/fake_studyset_gateway.dart';
import '../../../support/loaded_maps.dart';
import '../../../support/pump_app.dart';
import '../../../support/test_dependencies.dart';

View file

@ -7,11 +7,10 @@ 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/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: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

@ -5,12 +5,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_learn_flutter/src/feature/card/widget/card_tile.dart';
import 'package:material_ui/material_ui.dart' hide Card;
import '../../../support/fake_card_gateway.dart';
import '../../../support/fake_studyset_gateway.dart';
import '../../../support/pump_app.dart';
import '../../../support/test_dependencies.dart';

View file

@ -9,6 +9,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/editor/ruby_marks.dart';
import 'package:halkyon_learn_flutter/src/feature/dictionary/data/dictionary_adder.dart';
import 'package:halkyon_learn_flutter/src/feature/dictionary/data/dictionary_repository.dart';
@ -18,13 +19,10 @@ import 'package:halkyon_learn_flutter/src/feature/dictionary/model/dictionary.da
import 'package:halkyon_learn_flutter/src/feature/settings/data/settings_repository.dart';
import 'package:halkyon_learn_flutter/src/feature/study/data/review_repository.dart';
import '../../support/fake_card_gateway.dart';
import '../../support/fake_device_dictionaries.dart';
import '../../support/fake_dictionary_gateway.dart';
import '../../support/fake_review_gateway.dart';
import '../../support/fake_settings_gateway.dart';
import '../../support/fake_studyset_gateway.dart';
import '../../support/fake_tag_gateway.dart';
final taberu = word(
'1358280',

View file

@ -6,13 +6,12 @@ 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/kanji/data/stroke_repository.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/loaded_strokes.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