feat(app,learn,todo): the assistants in Settings and on the operator's screen
ADR 0021, step 2's app half. halkyon_app asks the module's assistant
endpoints (ServerpodAssistantGateway, ServerpodAssistantOperatorGateway)
and a person's own provider directly (halkyon_llm), behind
AssistantRepository: an AssistantChoice is a GrantedAssistant, asked
through the instance, which counts it, or a PersonalAssistant with the
person's own key, which the instance never sees. assistant_messages.dart
turns a conversation into the endpoint's models and back.
AssistantsCard, for an app's settings: what is granted and left of its
limits, agreed to once on a sheet saying what goes where, the person's
use by UTC month, the instance's privacy page, and their own assistants
where the operator allows own keys (kept ones stay listed, unasked,
where it doesn't). AssistantGrantsCard on InstanceOperatorScreen: grants
by handle with their limits in the pricing's unit, changed and taken
back after asking, everyone's use by month; a found account's menu
grants too. Nothing shows where nothing is offered or can be read.
package:halkyon_app/assistant_models.dart holds the models in pure Dart.
Learn keeps own assistants in the person's settings (ownAssistants,
schema 12, SettingsOwnAssistants); SettingsRepository.update refuses
(OwnAssistantRefused) to write a key where the settings would not be
sealed (SettingsGateway.seals). Learn depends on halkyon_llm directly.
Todo wires the gateways; a member offers no assistant. No feature asks
an assistant yet (step 3).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-25 18:59:41 +01:00
|
|
|
import 'package:flutter_test/flutter_test.dart';
|
|
|
|
|
import 'package:halkyon_app/halkyon_app.dart';
|
|
|
|
|
import 'package:halkyon_app/testing.dart';
|
|
|
|
|
import 'package:halkyon_llm/halkyon_llm.dart';
|
|
|
|
|
import 'package:halkyon_ui/halkyon_ui.dart';
|
|
|
|
|
import 'package:material_ui/material_ui.dart';
|
|
|
|
|
|
|
|
|
|
/// An app's settings' Assistants card (Halkyon Learn's ADR 0021, points 6,
|
feat(app): the operator's overview of screens, grants to several people at once with one budget each, assistants by provider
Halkyon Learn's ADR 0021, D57 to D63, on halkyon_base's budgets and
effort:
- The operator's screen is an overview (Waiting, Operators, Accounts,
Assistants, each with its count) whose rows open screens of their own,
read 50 at a time as they scroll (OperatorListCubit, OperatorPage), so
hundreds of accounts push nothing else down (D62).
- A handle is picked among the accounts there are (AccountSuggestionField):
appointing an operator and granting take no other (D61).
- AssistantGrantsScreen replaces AssistantGrantsCard: the assistants set
up by provider, with their efforts and prices; the people granted, found
by their handle's start; one sheet granting several assistants, a
provider's at once, to several people, each with one budget in money and
tokens a day and a month, or changing one person's (D58, D59).
- Assistants are grouped by provider wherever they are listed, each under
its name with a dot in its colour (ProviderLabel, D60).
- AssistantsCard shows what is left of the person's budget for all their
assistants together; the months of the figures run oldest first (D57).
- AssistantChoice says the efforts a question may ask at and the ceiling
on output each asks for (D63); a person's own key gets none and keeps
4,096 tokens.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-26 16:05:49 +01:00
|
|
|
/// 9 and 10, D58, D60): what is granted, by provider, and left of the
|
|
|
|
|
/// budget for them all, agreeing to what goes where once, the person's
|
|
|
|
|
/// use, and assistants with their own key where allowed.
|
feat(app,learn,todo): the assistants in Settings and on the operator's screen
ADR 0021, step 2's app half. halkyon_app asks the module's assistant
endpoints (ServerpodAssistantGateway, ServerpodAssistantOperatorGateway)
and a person's own provider directly (halkyon_llm), behind
AssistantRepository: an AssistantChoice is a GrantedAssistant, asked
through the instance, which counts it, or a PersonalAssistant with the
person's own key, which the instance never sees. assistant_messages.dart
turns a conversation into the endpoint's models and back.
AssistantsCard, for an app's settings: what is granted and left of its
limits, agreed to once on a sheet saying what goes where, the person's
use by UTC month, the instance's privacy page, and their own assistants
where the operator allows own keys (kept ones stay listed, unasked,
where it doesn't). AssistantGrantsCard on InstanceOperatorScreen: grants
by handle with their limits in the pricing's unit, changed and taken
back after asking, everyone's use by month; a found account's menu
grants too. Nothing shows where nothing is offered or can be read.
package:halkyon_app/assistant_models.dart holds the models in pure Dart.
Learn keeps own assistants in the person's settings (ownAssistants,
schema 12, SettingsOwnAssistants); SettingsRepository.update refuses
(OwnAssistantRefused) to write a key where the settings would not be
sealed (SettingsGateway.seals). Learn depends on halkyon_llm directly.
Todo wires the gateways; a member offers no assistant. No feature asks
an assistant yet (step 3).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-25 18:59:41 +01:00
|
|
|
void main() {
|
|
|
|
|
late TestAppDependencies dependencies;
|
|
|
|
|
late FakeAssistantGateway gateway;
|
|
|
|
|
late MemoryOwnAssistantStore store;
|
|
|
|
|
|
|
|
|
|
const claude = OfferedAssistant(
|
|
|
|
|
id: 'claude',
|
|
|
|
|
name: 'Claude',
|
|
|
|
|
protocol: 'anthropic',
|
|
|
|
|
host: 'api.anthropic.com',
|
|
|
|
|
model: 'claude-sonnet-5-5',
|
|
|
|
|
images: true,
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
setUp(() {
|
|
|
|
|
dependencies = TestAppDependencies();
|
|
|
|
|
gateway = dependencies.assistantGateway
|
|
|
|
|
..offer = const AssistantsOffer(
|
|
|
|
|
assistants: [claude],
|
|
|
|
|
ownKeys: true,
|
|
|
|
|
currency: 'EUR',
|
feat(app): the operator's overview of screens, grants to several people at once with one budget each, assistants by provider
Halkyon Learn's ADR 0021, D57 to D63, on halkyon_base's budgets and
effort:
- The operator's screen is an overview (Waiting, Operators, Accounts,
Assistants, each with its count) whose rows open screens of their own,
read 50 at a time as they scroll (OperatorListCubit, OperatorPage), so
hundreds of accounts push nothing else down (D62).
- A handle is picked among the accounts there are (AccountSuggestionField):
appointing an operator and granting take no other (D61).
- AssistantGrantsScreen replaces AssistantGrantsCard: the assistants set
up by provider, with their efforts and prices; the people granted, found
by their handle's start; one sheet granting several assistants, a
provider's at once, to several people, each with one budget in money and
tokens a day and a month, or changing one person's (D58, D59).
- Assistants are grouped by provider wherever they are listed, each under
its name with a dot in its colour (ProviderLabel, D60).
- AssistantsCard shows what is left of the person's budget for all their
assistants together; the months of the figures run oldest first (D57).
- AssistantChoice says the efforts a question may ask at and the ceiling
on output each asks for (D63); a person's own key gets none and keeps
4,096 tokens.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-26 16:05:49 +01:00
|
|
|
budget: AssistantBudget(dailyCostMicros: 1000000),
|
|
|
|
|
usedToday: BudgetUse(costMicros: 8100, tokens: 900),
|
|
|
|
|
usedThisMonth: BudgetUse(costMicros: 8100, tokens: 900),
|
feat(app,learn,todo): the assistants in Settings and on the operator's screen
ADR 0021, step 2's app half. halkyon_app asks the module's assistant
endpoints (ServerpodAssistantGateway, ServerpodAssistantOperatorGateway)
and a person's own provider directly (halkyon_llm), behind
AssistantRepository: an AssistantChoice is a GrantedAssistant, asked
through the instance, which counts it, or a PersonalAssistant with the
person's own key, which the instance never sees. assistant_messages.dart
turns a conversation into the endpoint's models and back.
AssistantsCard, for an app's settings: what is granted and left of its
limits, agreed to once on a sheet saying what goes where, the person's
use by UTC month, the instance's privacy page, and their own assistants
where the operator allows own keys (kept ones stay listed, unasked,
where it doesn't). AssistantGrantsCard on InstanceOperatorScreen: grants
by handle with their limits in the pricing's unit, changed and taken
back after asking, everyone's use by month; a found account's menu
grants too. Nothing shows where nothing is offered or can be read.
package:halkyon_app/assistant_models.dart holds the models in pure Dart.
Learn keeps own assistants in the person's settings (ownAssistants,
schema 12, SettingsOwnAssistants); SettingsRepository.update refuses
(OwnAssistantRefused) to write a key where the settings would not be
sealed (SettingsGateway.seals). Learn depends on halkyon_llm directly.
Todo wires the gateways; a member offers no assistant. No feature asks
an assistant yet (step 3).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-25 18:59:41 +01:00
|
|
|
);
|
|
|
|
|
store = MemoryOwnAssistantStore();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
Future<void> show(WidgetTester tester, {bool keepsOwn = true}) async {
|
|
|
|
|
tester.view.physicalSize = const Size(412, 1600);
|
|
|
|
|
tester.view.devicePixelRatio = 1;
|
|
|
|
|
addTearDown(tester.view.reset);
|
|
|
|
|
await tester.pumpWidget(
|
|
|
|
|
testApp(
|
|
|
|
|
Scaffold(
|
|
|
|
|
body: SingleChildScrollView(
|
|
|
|
|
child: AssistantsCard(ownAssistants: keepsOwn ? store : null),
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
dependencies: dependencies,
|
|
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
await tester.pumpAndSettle();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Finder inSheet(Finder finder) =>
|
|
|
|
|
find.descendant(of: find.byType(SheetScaffold), matching: finder);
|
|
|
|
|
|
|
|
|
|
Future<void> tap(WidgetTester tester, Finder finder) async {
|
|
|
|
|
await tester.ensureVisible(finder);
|
|
|
|
|
await tester.tap(finder);
|
|
|
|
|
await tester.pumpAndSettle();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
testWidgets('shows nothing where nothing is offered and own keys are not '
|
|
|
|
|
'allowed', (tester) async {
|
|
|
|
|
gateway.offer = AssistantsOffer.none;
|
|
|
|
|
await show(tester);
|
|
|
|
|
|
|
|
|
|
expect(find.text('Assistants'), findsNothing);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
testWidgets("shows nothing where the offer can't be read", (tester) async {
|
|
|
|
|
gateway.failing = const AssistantFailed(AssistantProblem.failed);
|
|
|
|
|
await show(tester);
|
|
|
|
|
|
|
|
|
|
expect(find.text('Assistants'), findsNothing);
|
|
|
|
|
});
|
|
|
|
|
|
feat(app): the operator's overview of screens, grants to several people at once with one budget each, assistants by provider
Halkyon Learn's ADR 0021, D57 to D63, on halkyon_base's budgets and
effort:
- The operator's screen is an overview (Waiting, Operators, Accounts,
Assistants, each with its count) whose rows open screens of their own,
read 50 at a time as they scroll (OperatorListCubit, OperatorPage), so
hundreds of accounts push nothing else down (D62).
- A handle is picked among the accounts there are (AccountSuggestionField):
appointing an operator and granting take no other (D61).
- AssistantGrantsScreen replaces AssistantGrantsCard: the assistants set
up by provider, with their efforts and prices; the people granted, found
by their handle's start; one sheet granting several assistants, a
provider's at once, to several people, each with one budget in money and
tokens a day and a month, or changing one person's (D58, D59).
- Assistants are grouped by provider wherever they are listed, each under
its name with a dot in its colour (ProviderLabel, D60).
- AssistantsCard shows what is left of the person's budget for all their
assistants together; the months of the figures run oldest first (D57).
- AssistantChoice says the efforts a question may ask at and the ceiling
on output each asks for (D63); a person's own key gets none and keeps
4,096 tokens.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-26 16:05:49 +01:00
|
|
|
testWidgets('shows who answers, and what is left of the budget for them '
|
|
|
|
|
'all', (tester) async {
|
feat(app,learn,todo): the assistants in Settings and on the operator's screen
ADR 0021, step 2's app half. halkyon_app asks the module's assistant
endpoints (ServerpodAssistantGateway, ServerpodAssistantOperatorGateway)
and a person's own provider directly (halkyon_llm), behind
AssistantRepository: an AssistantChoice is a GrantedAssistant, asked
through the instance, which counts it, or a PersonalAssistant with the
person's own key, which the instance never sees. assistant_messages.dart
turns a conversation into the endpoint's models and back.
AssistantsCard, for an app's settings: what is granted and left of its
limits, agreed to once on a sheet saying what goes where, the person's
use by UTC month, the instance's privacy page, and their own assistants
where the operator allows own keys (kept ones stay listed, unasked,
where it doesn't). AssistantGrantsCard on InstanceOperatorScreen: grants
by handle with their limits in the pricing's unit, changed and taken
back after asking, everyone's use by month; a found account's menu
grants too. Nothing shows where nothing is offered or can be read.
package:halkyon_app/assistant_models.dart holds the models in pure Dart.
Learn keeps own assistants in the person's settings (ownAssistants,
schema 12, SettingsOwnAssistants); SettingsRepository.update refuses
(OwnAssistantRefused) to write a key where the settings would not be
sealed (SettingsGateway.seals). Learn depends on halkyon_llm directly.
Todo wires the gateways; a member offers no assistant. No feature asks
an assistant yet (step 3).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-25 18:59:41 +01:00
|
|
|
await show(tester);
|
|
|
|
|
|
|
|
|
|
expect(find.text('Assistants'), findsOneWidget);
|
feat(app): the operator's overview of screens, grants to several people at once with one budget each, assistants by provider
Halkyon Learn's ADR 0021, D57 to D63, on halkyon_base's budgets and
effort:
- The operator's screen is an overview (Waiting, Operators, Accounts,
Assistants, each with its count) whose rows open screens of their own,
read 50 at a time as they scroll (OperatorListCubit, OperatorPage), so
hundreds of accounts push nothing else down (D62).
- A handle is picked among the accounts there are (AccountSuggestionField):
appointing an operator and granting take no other (D61).
- AssistantGrantsScreen replaces AssistantGrantsCard: the assistants set
up by provider, with their efforts and prices; the people granted, found
by their handle's start; one sheet granting several assistants, a
provider's at once, to several people, each with one budget in money and
tokens a day and a month, or changing one person's (D58, D59).
- Assistants are grouped by provider wherever they are listed, each under
its name with a dot in its colour (ProviderLabel, D60).
- AssistantsCard shows what is left of the person's budget for all their
assistants together; the months of the figures run oldest first (D57).
- AssistantChoice says the efforts a question may ask at and the ceiling
on output each asks for (D63); a person's own key gets none and keeps
4,096 tokens.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-26 16:05:49 +01:00
|
|
|
expect(find.text('Your budget, for all of them'), findsOneWidget);
|
|
|
|
|
expect(find.text('€0.99 of €1.00 left today'), findsOneWidget);
|
|
|
|
|
expect(find.text('Anthropic'), findsOneWidget);
|
feat(app,learn,todo): the assistants in Settings and on the operator's screen
ADR 0021, step 2's app half. halkyon_app asks the module's assistant
endpoints (ServerpodAssistantGateway, ServerpodAssistantOperatorGateway)
and a person's own provider directly (halkyon_llm), behind
AssistantRepository: an AssistantChoice is a GrantedAssistant, asked
through the instance, which counts it, or a PersonalAssistant with the
person's own key, which the instance never sees. assistant_messages.dart
turns a conversation into the endpoint's models and back.
AssistantsCard, for an app's settings: what is granted and left of its
limits, agreed to once on a sheet saying what goes where, the person's
use by UTC month, the instance's privacy page, and their own assistants
where the operator allows own keys (kept ones stay listed, unasked,
where it doesn't). AssistantGrantsCard on InstanceOperatorScreen: grants
by handle with their limits in the pricing's unit, changed and taken
back after asking, everyone's use by month; a found account's menu
grants too. Nothing shows where nothing is offered or can be read.
package:halkyon_app/assistant_models.dart holds the models in pure Dart.
Learn keeps own assistants in the person's settings (ownAssistants,
schema 12, SettingsOwnAssistants); SettingsRepository.update refuses
(OwnAssistantRefused) to write a key where the settings would not be
sealed (SettingsGateway.seals). Learn depends on halkyon_llm directly.
Todo wires the gateways; a member offers no assistant. No feature asks
an assistant yet (step 3).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-25 18:59:41 +01:00
|
|
|
expect(find.text('Claude'), findsOneWidget);
|
feat(app): the operator's overview of screens, grants to several people at once with one budget each, assistants by provider
Halkyon Learn's ADR 0021, D57 to D63, on halkyon_base's budgets and
effort:
- The operator's screen is an overview (Waiting, Operators, Accounts,
Assistants, each with its count) whose rows open screens of their own,
read 50 at a time as they scroll (OperatorListCubit, OperatorPage), so
hundreds of accounts push nothing else down (D62).
- A handle is picked among the accounts there are (AccountSuggestionField):
appointing an operator and granting take no other (D61).
- AssistantGrantsScreen replaces AssistantGrantsCard: the assistants set
up by provider, with their efforts and prices; the people granted, found
by their handle's start; one sheet granting several assistants, a
provider's at once, to several people, each with one budget in money and
tokens a day and a month, or changing one person's (D58, D59).
- Assistants are grouped by provider wherever they are listed, each under
its name with a dot in its colour (ProviderLabel, D60).
- AssistantsCard shows what is left of the person's budget for all their
assistants together; the months of the figures run oldest first (D57).
- AssistantChoice says the efforts a question may ask at and the ceiling
on output each asks for (D63); a person's own key gets none and keeps
4,096 tokens.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-26 16:05:49 +01:00
|
|
|
expect(
|
|
|
|
|
find.text('claude-sonnet-5-5 at api.anthropic.com\nRead and agree'),
|
|
|
|
|
findsOneWidget,
|
|
|
|
|
);
|
|
|
|
|
expect(find.text('Your use'), findsOneWidget);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
testWidgets('a budget in tokens names them once', (tester) async {
|
|
|
|
|
gateway.offer = const AssistantsOffer(
|
|
|
|
|
assistants: [claude],
|
|
|
|
|
budget: AssistantBudget(dailyTokens: 100000, monthlyTokens: 2000000),
|
|
|
|
|
usedToday: BudgetUse(tokens: 350000),
|
|
|
|
|
usedThisMonth: BudgetUse(tokens: 350000),
|
|
|
|
|
);
|
|
|
|
|
await show(tester);
|
|
|
|
|
|
feat(app,learn,todo): the assistants in Settings and on the operator's screen
ADR 0021, step 2's app half. halkyon_app asks the module's assistant
endpoints (ServerpodAssistantGateway, ServerpodAssistantOperatorGateway)
and a person's own provider directly (halkyon_llm), behind
AssistantRepository: an AssistantChoice is a GrantedAssistant, asked
through the instance, which counts it, or a PersonalAssistant with the
person's own key, which the instance never sees. assistant_messages.dart
turns a conversation into the endpoint's models and back.
AssistantsCard, for an app's settings: what is granted and left of its
limits, agreed to once on a sheet saying what goes where, the person's
use by UTC month, the instance's privacy page, and their own assistants
where the operator allows own keys (kept ones stay listed, unasked,
where it doesn't). AssistantGrantsCard on InstanceOperatorScreen: grants
by handle with their limits in the pricing's unit, changed and taken
back after asking, everyone's use by month; a found account's menu
grants too. Nothing shows where nothing is offered or can be read.
package:halkyon_app/assistant_models.dart holds the models in pure Dart.
Learn keeps own assistants in the person's settings (ownAssistants,
schema 12, SettingsOwnAssistants); SettingsRepository.update refuses
(OwnAssistantRefused) to write a key where the settings would not be
sealed (SettingsGateway.seals). Learn depends on halkyon_llm directly.
Todo wires the gateways; a member offers no assistant. No feature asks
an assistant yet (step 3).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-25 18:59:41 +01:00
|
|
|
expect(
|
|
|
|
|
find.text(
|
feat(app): the operator's overview of screens, grants to several people at once with one budget each, assistants by provider
Halkyon Learn's ADR 0021, D57 to D63, on halkyon_base's budgets and
effort:
- The operator's screen is an overview (Waiting, Operators, Accounts,
Assistants, each with its count) whose rows open screens of their own,
read 50 at a time as they scroll (OperatorListCubit, OperatorPage), so
hundreds of accounts push nothing else down (D62).
- A handle is picked among the accounts there are (AccountSuggestionField):
appointing an operator and granting take no other (D61).
- AssistantGrantsScreen replaces AssistantGrantsCard: the assistants set
up by provider, with their efforts and prices; the people granted, found
by their handle's start; one sheet granting several assistants, a
provider's at once, to several people, each with one budget in money and
tokens a day and a month, or changing one person's (D58, D59).
- Assistants are grouped by provider wherever they are listed, each under
its name with a dot in its colour (ProviderLabel, D60).
- AssistantsCard shows what is left of the person's budget for all their
assistants together; the months of the figures run oldest first (D57).
- AssistantChoice says the efforts a question may ask at and the ceiling
on output each asks for (D63); a person's own key gets none and keeps
4,096 tokens.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-26 16:05:49 +01:00
|
|
|
'0 of 100,000 tokens left today\n'
|
|
|
|
|
'1,650,000 of 2,000,000 tokens left this month',
|
feat(app,learn,todo): the assistants in Settings and on the operator's screen
ADR 0021, step 2's app half. halkyon_app asks the module's assistant
endpoints (ServerpodAssistantGateway, ServerpodAssistantOperatorGateway)
and a person's own provider directly (halkyon_llm), behind
AssistantRepository: an AssistantChoice is a GrantedAssistant, asked
through the instance, which counts it, or a PersonalAssistant with the
person's own key, which the instance never sees. assistant_messages.dart
turns a conversation into the endpoint's models and back.
AssistantsCard, for an app's settings: what is granted and left of its
limits, agreed to once on a sheet saying what goes where, the person's
use by UTC month, the instance's privacy page, and their own assistants
where the operator allows own keys (kept ones stay listed, unasked,
where it doesn't). AssistantGrantsCard on InstanceOperatorScreen: grants
by handle with their limits in the pricing's unit, changed and taken
back after asking, everyone's use by month; a found account's menu
grants too. Nothing shows where nothing is offered or can be read.
package:halkyon_app/assistant_models.dart holds the models in pure Dart.
Learn keeps own assistants in the person's settings (ownAssistants,
schema 12, SettingsOwnAssistants); SettingsRepository.update refuses
(OwnAssistantRefused) to write a key where the settings would not be
sealed (SettingsGateway.seals). Learn depends on halkyon_llm directly.
Todo wires the gateways; a member offers no assistant. No feature asks
an assistant yet (step 3).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-25 18:59:41 +01:00
|
|
|
),
|
|
|
|
|
findsOneWidget,
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
|
feat(app): the operator's overview of screens, grants to several people at once with one budget each, assistants by provider
Halkyon Learn's ADR 0021, D57 to D63, on halkyon_base's budgets and
effort:
- The operator's screen is an overview (Waiting, Operators, Accounts,
Assistants, each with its count) whose rows open screens of their own,
read 50 at a time as they scroll (OperatorListCubit, OperatorPage), so
hundreds of accounts push nothing else down (D62).
- A handle is picked among the accounts there are (AccountSuggestionField):
appointing an operator and granting take no other (D61).
- AssistantGrantsScreen replaces AssistantGrantsCard: the assistants set
up by provider, with their efforts and prices; the people granted, found
by their handle's start; one sheet granting several assistants, a
provider's at once, to several people, each with one budget in money and
tokens a day and a month, or changing one person's (D58, D59).
- Assistants are grouped by provider wherever they are listed, each under
its name with a dot in its colour (ProviderLabel, D60).
- AssistantsCard shows what is left of the person's budget for all their
assistants together; the months of the figures run oldest first (D57).
- AssistantChoice says the efforts a question may ask at and the ceiling
on output each asks for (D63); a person's own key gets none and keeps
4,096 tokens.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-26 16:05:49 +01:00
|
|
|
testWidgets('a budget without limits says so', (tester) async {
|
|
|
|
|
gateway.offer = const AssistantsOffer(assistants: [claude]);
|
|
|
|
|
await show(tester);
|
|
|
|
|
|
|
|
|
|
expect(find.text('No limits'), findsOneWidget);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
testWidgets('groups the assistants by provider, in the order the operator '
|
|
|
|
|
'set them', (tester) async {
|
feat(app,learn,todo): the assistants in Settings and on the operator's screen
ADR 0021, step 2's app half. halkyon_app asks the module's assistant
endpoints (ServerpodAssistantGateway, ServerpodAssistantOperatorGateway)
and a person's own provider directly (halkyon_llm), behind
AssistantRepository: an AssistantChoice is a GrantedAssistant, asked
through the instance, which counts it, or a PersonalAssistant with the
person's own key, which the instance never sees. assistant_messages.dart
turns a conversation into the endpoint's models and back.
AssistantsCard, for an app's settings: what is granted and left of its
limits, agreed to once on a sheet saying what goes where, the person's
use by UTC month, the instance's privacy page, and their own assistants
where the operator allows own keys (kept ones stay listed, unasked,
where it doesn't). AssistantGrantsCard on InstanceOperatorScreen: grants
by handle with their limits in the pricing's unit, changed and taken
back after asking, everyone's use by month; a found account's menu
grants too. Nothing shows where nothing is offered or can be read.
package:halkyon_app/assistant_models.dart holds the models in pure Dart.
Learn keeps own assistants in the person's settings (ownAssistants,
schema 12, SettingsOwnAssistants); SettingsRepository.update refuses
(OwnAssistantRefused) to write a key where the settings would not be
sealed (SettingsGateway.seals). Learn depends on halkyon_llm directly.
Todo wires the gateways; a member offers no assistant. No feature asks
an assistant yet (step 3).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-25 18:59:41 +01:00
|
|
|
gateway.offer = const AssistantsOffer(
|
|
|
|
|
assistants: [
|
feat(app): the operator's overview of screens, grants to several people at once with one budget each, assistants by provider
Halkyon Learn's ADR 0021, D57 to D63, on halkyon_base's budgets and
effort:
- The operator's screen is an overview (Waiting, Operators, Accounts,
Assistants, each with its count) whose rows open screens of their own,
read 50 at a time as they scroll (OperatorListCubit, OperatorPage), so
hundreds of accounts push nothing else down (D62).
- A handle is picked among the accounts there are (AccountSuggestionField):
appointing an operator and granting take no other (D61).
- AssistantGrantsScreen replaces AssistantGrantsCard: the assistants set
up by provider, with their efforts and prices; the people granted, found
by their handle's start; one sheet granting several assistants, a
provider's at once, to several people, each with one budget in money and
tokens a day and a month, or changing one person's (D58, D59).
- Assistants are grouped by provider wherever they are listed, each under
its name with a dot in its colour (ProviderLabel, D60).
- AssistantsCard shows what is left of the person's budget for all their
assistants together; the months of the figures run oldest first (D57).
- AssistantChoice says the efforts a question may ask at and the ceiling
on output each asks for (D63); a person's own key gets none and keeps
4,096 tokens.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-26 16:05:49 +01:00
|
|
|
claude,
|
feat(app,learn,todo): the assistants in Settings and on the operator's screen
ADR 0021, step 2's app half. halkyon_app asks the module's assistant
endpoints (ServerpodAssistantGateway, ServerpodAssistantOperatorGateway)
and a person's own provider directly (halkyon_llm), behind
AssistantRepository: an AssistantChoice is a GrantedAssistant, asked
through the instance, which counts it, or a PersonalAssistant with the
person's own key, which the instance never sees. assistant_messages.dart
turns a conversation into the endpoint's models and back.
AssistantsCard, for an app's settings: what is granted and left of its
limits, agreed to once on a sheet saying what goes where, the person's
use by UTC month, the instance's privacy page, and their own assistants
where the operator allows own keys (kept ones stay listed, unasked,
where it doesn't). AssistantGrantsCard on InstanceOperatorScreen: grants
by handle with their limits in the pricing's unit, changed and taken
back after asking, everyone's use by month; a found account's menu
grants too. Nothing shows where nothing is offered or can be read.
package:halkyon_app/assistant_models.dart holds the models in pure Dart.
Learn keeps own assistants in the person's settings (ownAssistants,
schema 12, SettingsOwnAssistants); SettingsRepository.update refuses
(OwnAssistantRefused) to write a key where the settings would not be
sealed (SettingsGateway.seals). Learn depends on halkyon_llm directly.
Todo wires the gateways; a member offers no assistant. No feature asks
an assistant yet (step 3).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-25 18:59:41 +01:00
|
|
|
OfferedAssistant(
|
feat(app): the operator's overview of screens, grants to several people at once with one budget each, assistants by provider
Halkyon Learn's ADR 0021, D57 to D63, on halkyon_base's budgets and
effort:
- The operator's screen is an overview (Waiting, Operators, Accounts,
Assistants, each with its count) whose rows open screens of their own,
read 50 at a time as they scroll (OperatorListCubit, OperatorPage), so
hundreds of accounts push nothing else down (D62).
- A handle is picked among the accounts there are (AccountSuggestionField):
appointing an operator and granting take no other (D61).
- AssistantGrantsScreen replaces AssistantGrantsCard: the assistants set
up by provider, with their efforts and prices; the people granted, found
by their handle's start; one sheet granting several assistants, a
provider's at once, to several people, each with one budget in money and
tokens a day and a month, or changing one person's (D58, D59).
- Assistants are grouped by provider wherever they are listed, each under
its name with a dot in its colour (ProviderLabel, D60).
- AssistantsCard shows what is left of the person's budget for all their
assistants together; the months of the figures run oldest first (D57).
- AssistantChoice says the efforts a question may ask at and the ceiling
on output each asks for (D63); a person's own key gets none and keeps
4,096 tokens.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-26 16:05:49 +01:00
|
|
|
id: 'gemini',
|
|
|
|
|
name: 'Gemini',
|
feat(app,learn,todo): the assistants in Settings and on the operator's screen
ADR 0021, step 2's app half. halkyon_app asks the module's assistant
endpoints (ServerpodAssistantGateway, ServerpodAssistantOperatorGateway)
and a person's own provider directly (halkyon_llm), behind
AssistantRepository: an AssistantChoice is a GrantedAssistant, asked
through the instance, which counts it, or a PersonalAssistant with the
person's own key, which the instance never sees. assistant_messages.dart
turns a conversation into the endpoint's models and back.
AssistantsCard, for an app's settings: what is granted and left of its
limits, agreed to once on a sheet saying what goes where, the person's
use by UTC month, the instance's privacy page, and their own assistants
where the operator allows own keys (kept ones stay listed, unasked,
where it doesn't). AssistantGrantsCard on InstanceOperatorScreen: grants
by handle with their limits in the pricing's unit, changed and taken
back after asking, everyone's use by month; a found account's menu
grants too. Nothing shows where nothing is offered or can be read.
package:halkyon_app/assistant_models.dart holds the models in pure Dart.
Learn keeps own assistants in the person's settings (ownAssistants,
schema 12, SettingsOwnAssistants); SettingsRepository.update refuses
(OwnAssistantRefused) to write a key where the settings would not be
sealed (SettingsGateway.seals). Learn depends on halkyon_llm directly.
Todo wires the gateways; a member offers no assistant. No feature asks
an assistant yet (step 3).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-25 18:59:41 +01:00
|
|
|
protocol: 'openai',
|
feat(app): the operator's overview of screens, grants to several people at once with one budget each, assistants by provider
Halkyon Learn's ADR 0021, D57 to D63, on halkyon_base's budgets and
effort:
- The operator's screen is an overview (Waiting, Operators, Accounts,
Assistants, each with its count) whose rows open screens of their own,
read 50 at a time as they scroll (OperatorListCubit, OperatorPage), so
hundreds of accounts push nothing else down (D62).
- A handle is picked among the accounts there are (AccountSuggestionField):
appointing an operator and granting take no other (D61).
- AssistantGrantsScreen replaces AssistantGrantsCard: the assistants set
up by provider, with their efforts and prices; the people granted, found
by their handle's start; one sheet granting several assistants, a
provider's at once, to several people, each with one budget in money and
tokens a day and a month, or changing one person's (D58, D59).
- Assistants are grouped by provider wherever they are listed, each under
its name with a dot in its colour (ProviderLabel, D60).
- AssistantsCard shows what is left of the person's budget for all their
assistants together; the months of the figures run oldest first (D57).
- AssistantChoice says the efforts a question may ask at and the ceiling
on output each asks for (D63); a person's own key gets none and keeps
4,096 tokens.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-26 16:05:49 +01:00
|
|
|
host: 'generativelanguage.googleapis.com',
|
|
|
|
|
model: 'gemini-3-pro',
|
|
|
|
|
),
|
|
|
|
|
OfferedAssistant(
|
|
|
|
|
id: 'opus',
|
|
|
|
|
name: 'Opus',
|
|
|
|
|
protocol: 'anthropic',
|
|
|
|
|
host: 'api.anthropic.com',
|
|
|
|
|
model: 'claude-opus-5-5',
|
feat(app,learn,todo): the assistants in Settings and on the operator's screen
ADR 0021, step 2's app half. halkyon_app asks the module's assistant
endpoints (ServerpodAssistantGateway, ServerpodAssistantOperatorGateway)
and a person's own provider directly (halkyon_llm), behind
AssistantRepository: an AssistantChoice is a GrantedAssistant, asked
through the instance, which counts it, or a PersonalAssistant with the
person's own key, which the instance never sees. assistant_messages.dart
turns a conversation into the endpoint's models and back.
AssistantsCard, for an app's settings: what is granted and left of its
limits, agreed to once on a sheet saying what goes where, the person's
use by UTC month, the instance's privacy page, and their own assistants
where the operator allows own keys (kept ones stay listed, unasked,
where it doesn't). AssistantGrantsCard on InstanceOperatorScreen: grants
by handle with their limits in the pricing's unit, changed and taken
back after asking, everyone's use by month; a found account's menu
grants too. Nothing shows where nothing is offered or can be read.
package:halkyon_app/assistant_models.dart holds the models in pure Dart.
Learn keeps own assistants in the person's settings (ownAssistants,
schema 12, SettingsOwnAssistants); SettingsRepository.update refuses
(OwnAssistantRefused) to write a key where the settings would not be
sealed (SettingsGateway.seals). Learn depends on halkyon_llm directly.
Todo wires the gateways; a member offers no assistant. No feature asks
an assistant yet (step 3).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-25 18:59:41 +01:00
|
|
|
),
|
|
|
|
|
],
|
|
|
|
|
);
|
|
|
|
|
await show(tester);
|
|
|
|
|
|
feat(app): the operator's overview of screens, grants to several people at once with one budget each, assistants by provider
Halkyon Learn's ADR 0021, D57 to D63, on halkyon_base's budgets and
effort:
- The operator's screen is an overview (Waiting, Operators, Accounts,
Assistants, each with its count) whose rows open screens of their own,
read 50 at a time as they scroll (OperatorListCubit, OperatorPage), so
hundreds of accounts push nothing else down (D62).
- A handle is picked among the accounts there are (AccountSuggestionField):
appointing an operator and granting take no other (D61).
- AssistantGrantsScreen replaces AssistantGrantsCard: the assistants set
up by provider, with their efforts and prices; the people granted, found
by their handle's start; one sheet granting several assistants, a
provider's at once, to several people, each with one budget in money and
tokens a day and a month, or changing one person's (D58, D59).
- Assistants are grouped by provider wherever they are listed, each under
its name with a dot in its colour (ProviderLabel, D60).
- AssistantsCard shows what is left of the person's budget for all their
assistants together; the months of the figures run oldest first (D57).
- AssistantChoice says the efforts a question may ask at and the ceiling
on output each asks for (D63); a person's own key gets none and keeps
4,096 tokens.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-26 16:05:49 +01:00
|
|
|
final labels = [
|
|
|
|
|
for (final text in tester.widgetList<Text>(
|
|
|
|
|
find.descendant(
|
|
|
|
|
of: find.byType(AssistantsCard),
|
|
|
|
|
matching: find.byType(Text),
|
|
|
|
|
),
|
|
|
|
|
))
|
|
|
|
|
if (const {
|
|
|
|
|
'Anthropic',
|
|
|
|
|
'Google',
|
|
|
|
|
'Claude',
|
|
|
|
|
'Opus',
|
|
|
|
|
'Gemini',
|
|
|
|
|
}.contains(text.data))
|
|
|
|
|
text.data,
|
|
|
|
|
];
|
|
|
|
|
expect(labels, ['Anthropic', 'Claude', 'Opus', 'Google', 'Gemini']);
|
feat(app,learn,todo): the assistants in Settings and on the operator's screen
ADR 0021, step 2's app half. halkyon_app asks the module's assistant
endpoints (ServerpodAssistantGateway, ServerpodAssistantOperatorGateway)
and a person's own provider directly (halkyon_llm), behind
AssistantRepository: an AssistantChoice is a GrantedAssistant, asked
through the instance, which counts it, or a PersonalAssistant with the
person's own key, which the instance never sees. assistant_messages.dart
turns a conversation into the endpoint's models and back.
AssistantsCard, for an app's settings: what is granted and left of its
limits, agreed to once on a sheet saying what goes where, the person's
use by UTC month, the instance's privacy page, and their own assistants
where the operator allows own keys (kept ones stay listed, unasked,
where it doesn't). AssistantGrantsCard on InstanceOperatorScreen: grants
by handle with their limits in the pricing's unit, changed and taken
back after asking, everyone's use by month; a found account's menu
grants too. Nothing shows where nothing is offered or can be read.
package:halkyon_app/assistant_models.dart holds the models in pure Dart.
Learn keeps own assistants in the person's settings (ownAssistants,
schema 12, SettingsOwnAssistants); SettingsRepository.update refuses
(OwnAssistantRefused) to write a key where the settings would not be
sealed (SettingsGateway.seals). Learn depends on halkyon_llm directly.
Todo wires the gateways; a member offers no assistant. No feature asks
an assistant yet (step 3).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-25 18:59:41 +01:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
testWidgets('says what goes where before agreeing, and agrees only when '
|
|
|
|
|
'asked to (D40)', (tester) async {
|
|
|
|
|
await show(tester);
|
|
|
|
|
|
|
|
|
|
await tap(tester, find.text('Claude'));
|
|
|
|
|
expect(find.text('Before you ask Claude'), findsOneWidget);
|
|
|
|
|
expect(
|
|
|
|
|
find.text(
|
|
|
|
|
'It answers with the model claude-sonnet-5-5 at '
|
|
|
|
|
'api.anthropic.com.',
|
|
|
|
|
),
|
|
|
|
|
findsOneWidget,
|
|
|
|
|
);
|
|
|
|
|
expect(find.text('It reads the images you add.'), findsOneWidget);
|
|
|
|
|
expect(
|
|
|
|
|
find.text(
|
|
|
|
|
'Whoever runs test.example.org sees how much you use it (requests, '
|
|
|
|
|
'tokens, cost), never what you ask.',
|
|
|
|
|
),
|
|
|
|
|
findsOneWidget,
|
|
|
|
|
);
|
|
|
|
|
expect(gateway.calls, isNot(contains('agree claude')));
|
|
|
|
|
|
|
|
|
|
await tap(tester, find.widgetWithText(FilledButton, 'Agree'));
|
|
|
|
|
|
|
|
|
|
expect(gateway.calls, contains('agree claude'));
|
|
|
|
|
expect(find.text('Before you ask Claude'), findsNothing);
|
|
|
|
|
expect(find.textContaining('Read and agree'), findsNothing);
|
|
|
|
|
});
|
|
|
|
|
|
feat(learn): Explore by what a licence allows, a skeleton while the library loads, and an assistant's agreement per provider (ADR 0005, rule 12; ADR 0021, D66)
- Explore filters by what a studyset's licence lets a learner do: adapt
it (no NoDerivatives) and use it commercially (no NonCommercial),
which the instance reads from the release's licence
(ExploreQuery.adaptable, commercial) and the app the same way
(LicenseUse). An instance from before ignores both.
- While the studysets are read, the Library shows shimmering skeletons
of their cards rather than a spinner: halkyon_ui's new Shimmer and
SkeletonBox, still where the system asks for no animations.
- The scope's "Include private studysets" is offered only while the
person has a private studyset.
- An agreement to what goes where is per provider (protocol and host):
agreeing to one assistant agrees to every assistant of that provider,
those granted later too, and the agreement sheet says so. No
migration: it is written on every grant of the provider, so taking
one back keeps it for the others, and read per provider from any of
them.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-11-02 01:09:04 +01:00
|
|
|
testWidgets('one agreement holds for every assistant of its provider, '
|
|
|
|
|
'which the sheet says (D66)', (tester) async {
|
|
|
|
|
gateway.offer = const AssistantsOffer(
|
|
|
|
|
assistants: [
|
|
|
|
|
claude,
|
|
|
|
|
OfferedAssistant(
|
|
|
|
|
id: 'opus',
|
|
|
|
|
name: 'Opus',
|
|
|
|
|
protocol: 'anthropic',
|
|
|
|
|
host: 'api.anthropic.com',
|
|
|
|
|
model: 'claude-opus-5-5',
|
|
|
|
|
),
|
|
|
|
|
OfferedAssistant(
|
|
|
|
|
id: 'gemini',
|
|
|
|
|
name: 'Gemini',
|
|
|
|
|
protocol: 'openai',
|
|
|
|
|
host: 'generativelanguage.googleapis.com',
|
|
|
|
|
model: 'gemini-3-pro',
|
|
|
|
|
),
|
|
|
|
|
],
|
|
|
|
|
);
|
|
|
|
|
await show(tester);
|
|
|
|
|
expect(find.textContaining('Read and agree'), findsNWidgets(3));
|
|
|
|
|
|
|
|
|
|
await tap(tester, find.text('Claude'));
|
|
|
|
|
expect(
|
|
|
|
|
find.text('It holds for every assistant at api.anthropic.com.'),
|
|
|
|
|
findsOneWidget,
|
|
|
|
|
);
|
|
|
|
|
await tap(tester, find.widgetWithText(FilledButton, 'Agree'));
|
|
|
|
|
|
|
|
|
|
expect(find.textContaining('Read and agree'), findsOneWidget);
|
|
|
|
|
expect(gateway.calls.where((call) => call.startsWith('agree')), [
|
|
|
|
|
'agree claude',
|
|
|
|
|
]);
|
|
|
|
|
});
|
|
|
|
|
|
feat(app,learn,todo): the assistants in Settings and on the operator's screen
ADR 0021, step 2's app half. halkyon_app asks the module's assistant
endpoints (ServerpodAssistantGateway, ServerpodAssistantOperatorGateway)
and a person's own provider directly (halkyon_llm), behind
AssistantRepository: an AssistantChoice is a GrantedAssistant, asked
through the instance, which counts it, or a PersonalAssistant with the
person's own key, which the instance never sees. assistant_messages.dart
turns a conversation into the endpoint's models and back.
AssistantsCard, for an app's settings: what is granted and left of its
limits, agreed to once on a sheet saying what goes where, the person's
use by UTC month, the instance's privacy page, and their own assistants
where the operator allows own keys (kept ones stay listed, unasked,
where it doesn't). AssistantGrantsCard on InstanceOperatorScreen: grants
by handle with their limits in the pricing's unit, changed and taken
back after asking, everyone's use by month; a found account's menu
grants too. Nothing shows where nothing is offered or can be read.
package:halkyon_app/assistant_models.dart holds the models in pure Dart.
Learn keeps own assistants in the person's settings (ownAssistants,
schema 12, SettingsOwnAssistants); SettingsRepository.update refuses
(OwnAssistantRefused) to write a key where the settings would not be
sealed (SettingsGateway.seals). Learn depends on halkyon_llm directly.
Todo wires the gateways; a member offers no assistant. No feature asks
an assistant yet (step 3).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-25 18:59:41 +01:00
|
|
|
testWidgets('once agreed, the sheet says since when and asks nothing', (
|
|
|
|
|
tester,
|
|
|
|
|
) async {
|
|
|
|
|
gateway.offer = AssistantsOffer(
|
|
|
|
|
assistants: [
|
|
|
|
|
OfferedAssistant(
|
|
|
|
|
id: claude.id,
|
|
|
|
|
name: claude.name,
|
|
|
|
|
protocol: claude.protocol,
|
|
|
|
|
host: claude.host,
|
|
|
|
|
model: claude.model,
|
|
|
|
|
agreedAt: DateTime.utc(2026, 10, 25, 12),
|
|
|
|
|
),
|
|
|
|
|
],
|
|
|
|
|
);
|
|
|
|
|
await show(tester);
|
|
|
|
|
|
|
|
|
|
await tap(tester, find.text('Claude'));
|
|
|
|
|
|
|
|
|
|
expect(find.text('You agreed on Oct 25, 2026.'), findsOneWidget);
|
|
|
|
|
expect(find.widgetWithText(FilledButton, 'Agree'), findsNothing);
|
|
|
|
|
expect(find.text('It reads the images you add.'), findsNothing);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
testWidgets('a refused agreement is said so', (tester) async {
|
|
|
|
|
await show(tester);
|
|
|
|
|
|
|
|
|
|
await tap(tester, find.text('Claude'));
|
|
|
|
|
gateway.failing = const AssistantFailed(AssistantProblem.notGranted);
|
|
|
|
|
await tap(tester, find.widgetWithText(FilledButton, 'Agree'));
|
|
|
|
|
|
|
|
|
|
expect(
|
|
|
|
|
find.text("This assistant isn't granted to you any more."),
|
|
|
|
|
findsOneWidget,
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
testWidgets("the person's use, by UTC month", (tester) async {
|
|
|
|
|
final month = utcMonthOf(DateTime.now());
|
|
|
|
|
gateway.use = [
|
|
|
|
|
AssistantUse(
|
|
|
|
|
assistant: 'claude',
|
|
|
|
|
month: month,
|
|
|
|
|
requests: 1,
|
|
|
|
|
inputTokens: 900,
|
|
|
|
|
outputTokens: 100,
|
|
|
|
|
costMicros: 4500,
|
|
|
|
|
),
|
|
|
|
|
];
|
|
|
|
|
await show(tester);
|
|
|
|
|
|
|
|
|
|
await tap(tester, find.text('Your use'));
|
|
|
|
|
|
|
|
|
|
expect(inSheet(find.text('Claude')), findsOneWidget);
|
|
|
|
|
expect(
|
|
|
|
|
inSheet(find.text('1 request · 1,000 tokens · €0.00')),
|
|
|
|
|
findsOneWidget,
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
testWidgets('adds an assistant with an own key, kept in the settings', (
|
|
|
|
|
tester,
|
|
|
|
|
) async {
|
|
|
|
|
await show(tester);
|
|
|
|
|
|
|
|
|
|
await tap(tester, find.text('Add an assistant with your own key'));
|
|
|
|
|
await tester.enterText(
|
|
|
|
|
inSheet(find.widgetWithText(TextField, 'Name')),
|
|
|
|
|
'My GPT',
|
|
|
|
|
);
|
|
|
|
|
await tap(tester, find.text('OpenAI Responses'));
|
|
|
|
|
await tester.enterText(
|
|
|
|
|
inSheet(find.widgetWithText(TextField, 'Model')),
|
|
|
|
|
'gpt-5',
|
|
|
|
|
);
|
|
|
|
|
await tester.enterText(
|
|
|
|
|
inSheet(find.widgetWithText(TextField, 'Key')),
|
|
|
|
|
'sk-own',
|
|
|
|
|
);
|
|
|
|
|
await tap(tester, inSheet(find.widgetWithText(FilledButton, 'Save')));
|
|
|
|
|
|
|
|
|
|
final own = store.assistants.single;
|
|
|
|
|
expect(own.name, 'My GPT');
|
|
|
|
|
expect(own.protocol, LlmProtocol.openaiResponses);
|
|
|
|
|
expect(own.baseUrl, isNull);
|
|
|
|
|
expect(own.model, 'gpt-5');
|
|
|
|
|
expect(own.apiKey, 'sk-own');
|
|
|
|
|
expect(find.text('My GPT'), findsOneWidget);
|
|
|
|
|
expect(find.text('gpt-5 at api.openai.com'), findsOneWidget);
|
|
|
|
|
expect(find.text('sk-own'), findsNothing, reason: 'never shown');
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
testWidgets("an address that isn't https is refused before anything is "
|
|
|
|
|
'kept', (tester) async {
|
|
|
|
|
await show(tester);
|
|
|
|
|
|
|
|
|
|
await tap(tester, find.text('Add an assistant with your own key'));
|
|
|
|
|
await tester.enterText(
|
|
|
|
|
inSheet(find.widgetWithText(TextField, 'Name')),
|
|
|
|
|
'Remote',
|
|
|
|
|
);
|
|
|
|
|
await tester.enterText(
|
|
|
|
|
inSheet(find.widgetWithText(TextField, 'Address')),
|
|
|
|
|
'http://models.example.org/v1',
|
|
|
|
|
);
|
|
|
|
|
await tester.enterText(
|
|
|
|
|
inSheet(find.widgetWithText(TextField, 'Model')),
|
|
|
|
|
'qwen',
|
|
|
|
|
);
|
|
|
|
|
await tap(tester, inSheet(find.widgetWithText(FilledButton, 'Save')));
|
|
|
|
|
|
|
|
|
|
expect(
|
|
|
|
|
find.text('Only https, or http on your own network'),
|
|
|
|
|
findsOneWidget,
|
|
|
|
|
);
|
|
|
|
|
expect(store.assistants, isEmpty);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
testWidgets("a key isn't kept where this device can't seal the "
|
|
|
|
|
'settings', (tester) async {
|
|
|
|
|
store.unsealed = true;
|
|
|
|
|
await show(tester);
|
|
|
|
|
|
|
|
|
|
await tap(tester, find.text('Add an assistant with your own key'));
|
|
|
|
|
await tester.enterText(
|
|
|
|
|
inSheet(find.widgetWithText(TextField, 'Name')),
|
|
|
|
|
'Mine',
|
|
|
|
|
);
|
|
|
|
|
await tester.enterText(
|
|
|
|
|
inSheet(find.widgetWithText(TextField, 'Model')),
|
|
|
|
|
'claude-sonnet-5-5',
|
|
|
|
|
);
|
|
|
|
|
await tap(tester, inSheet(find.widgetWithText(FilledButton, 'Save')));
|
|
|
|
|
|
|
|
|
|
expect(
|
|
|
|
|
find.text(
|
|
|
|
|
"The key wasn't kept: this device can't encrypt your settings yet. "
|
|
|
|
|
"Open your account's keys here first.",
|
|
|
|
|
),
|
|
|
|
|
findsOneWidget,
|
|
|
|
|
);
|
|
|
|
|
expect(store.assistants, isEmpty);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
testWidgets('removing an own assistant asks first', (tester) async {
|
|
|
|
|
await store.save(
|
|
|
|
|
const OwnAssistant(
|
|
|
|
|
id: 'own-1',
|
|
|
|
|
name: 'My GPT',
|
|
|
|
|
protocol: LlmProtocol.openai,
|
|
|
|
|
model: 'gpt-5',
|
|
|
|
|
apiKey: 'sk-own',
|
|
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
await show(tester);
|
|
|
|
|
|
|
|
|
|
await tap(tester, find.text('My GPT'));
|
|
|
|
|
await tap(tester, find.text('Remove'));
|
|
|
|
|
expect(find.text('Remove My GPT?'), findsOneWidget);
|
|
|
|
|
await tap(tester, find.widgetWithText(FilledButton, 'Remove'));
|
|
|
|
|
|
|
|
|
|
expect(store.assistants, isEmpty);
|
|
|
|
|
expect(find.text('My GPT'), findsNothing);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
testWidgets("where own keys aren't allowed, those kept are said to go "
|
|
|
|
|
'unasked, and none can be added (D6)', (tester) async {
|
|
|
|
|
gateway.offer = AssistantsOffer.none;
|
|
|
|
|
await store.save(
|
|
|
|
|
const OwnAssistant(
|
|
|
|
|
id: 'own-1',
|
|
|
|
|
name: 'My GPT',
|
|
|
|
|
protocol: LlmProtocol.openai,
|
|
|
|
|
model: 'gpt-5',
|
|
|
|
|
apiKey: 'sk-own',
|
|
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
await show(tester);
|
feat(learn,app): explanations wait behind a question mark beside their title
What explains a setting or a section, rather than what is the person's
own, moves behind halkyon_ui's HelpButton, beside its title through the
new ExplainedTitle (the developer's review of 3 Nov 2026): in Learn's
settings (citations, the Pomodoro timer, grammar, the web search,
Jellyfin, the device's dictionaries, counting one's learning), the study
settings, export's progress, the Jellyfin tags and the study button's
back first; in halkyon_app, setting up a password, adding SimpleLogin,
own keys not allowed, the profile page's link back and the operator's
grant sheet.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-11-03 22:24:39 +01:00
|
|
|
const why =
|
feat(app,learn,todo): the assistants in Settings and on the operator's screen
ADR 0021, step 2's app half. halkyon_app asks the module's assistant
endpoints (ServerpodAssistantGateway, ServerpodAssistantOperatorGateway)
and a person's own provider directly (halkyon_llm), behind
AssistantRepository: an AssistantChoice is a GrantedAssistant, asked
through the instance, which counts it, or a PersonalAssistant with the
person's own key, which the instance never sees. assistant_messages.dart
turns a conversation into the endpoint's models and back.
AssistantsCard, for an app's settings: what is granted and left of its
limits, agreed to once on a sheet saying what goes where, the person's
use by UTC month, the instance's privacy page, and their own assistants
where the operator allows own keys (kept ones stay listed, unasked,
where it doesn't). AssistantGrantsCard on InstanceOperatorScreen: grants
by handle with their limits in the pricing's unit, changed and taken
back after asking, everyone's use by month; a found account's menu
grants too. Nothing shows where nothing is offered or can be read.
package:halkyon_app/assistant_models.dart holds the models in pure Dart.
Learn keeps own assistants in the person's settings (ownAssistants,
schema 12, SettingsOwnAssistants); SettingsRepository.update refuses
(OwnAssistantRefused) to write a key where the settings would not be
sealed (SettingsGateway.seals). Learn depends on halkyon_llm directly.
Todo wires the gateways; a member offers no assistant. No feature asks
an assistant yet (step 3).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-25 18:59:41 +01:00
|
|
|
"Whoever runs test.example.org doesn't allow own keys here, so the "
|
feat(learn,app): explanations wait behind a question mark beside their title
What explains a setting or a section, rather than what is the person's
own, moves behind halkyon_ui's HelpButton, beside its title through the
new ExplainedTitle (the developer's review of 3 Nov 2026): in Learn's
settings (citations, the Pomodoro timer, grammar, the web search,
Jellyfin, the device's dictionaries, counting one's learning), the study
settings, export's progress, the Jellyfin tags and the study button's
back first; in halkyon_app, setting up a password, adding SimpleLogin,
own keys not allowed, the profile page's link back and the operator's
grant sheet.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-11-03 22:24:39 +01:00
|
|
|
"app doesn't ask these.";
|
|
|
|
|
|
|
|
|
|
expect(find.text(why), findsNothing, reason: 'behind its question mark');
|
|
|
|
|
await tester.tap(find.byType(HelpButton));
|
|
|
|
|
await tester.pumpAndSettle();
|
|
|
|
|
expect(find.text(why), findsOneWidget);
|
|
|
|
|
await tester.tap(find.text('OK'));
|
|
|
|
|
await tester.pumpAndSettle();
|
feat(app,learn,todo): the assistants in Settings and on the operator's screen
ADR 0021, step 2's app half. halkyon_app asks the module's assistant
endpoints (ServerpodAssistantGateway, ServerpodAssistantOperatorGateway)
and a person's own provider directly (halkyon_llm), behind
AssistantRepository: an AssistantChoice is a GrantedAssistant, asked
through the instance, which counts it, or a PersonalAssistant with the
person's own key, which the instance never sees. assistant_messages.dart
turns a conversation into the endpoint's models and back.
AssistantsCard, for an app's settings: what is granted and left of its
limits, agreed to once on a sheet saying what goes where, the person's
use by UTC month, the instance's privacy page, and their own assistants
where the operator allows own keys (kept ones stay listed, unasked,
where it doesn't). AssistantGrantsCard on InstanceOperatorScreen: grants
by handle with their limits in the pricing's unit, changed and taken
back after asking, everyone's use by month; a found account's menu
grants too. Nothing shows where nothing is offered or can be read.
package:halkyon_app/assistant_models.dart holds the models in pure Dart.
Learn keeps own assistants in the person's settings (ownAssistants,
schema 12, SettingsOwnAssistants); SettingsRepository.update refuses
(OwnAssistantRefused) to write a key where the settings would not be
sealed (SettingsGateway.seals). Learn depends on halkyon_llm directly.
Todo wires the gateways; a member offers no assistant. No feature asks
an assistant yet (step 3).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-25 18:59:41 +01:00
|
|
|
expect(find.text('Add an assistant with your own key'), findsNothing);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
testWidgets('an app keeping no own assistants offers none', (tester) async {
|
|
|
|
|
await show(tester, keepsOwn: false);
|
|
|
|
|
|
|
|
|
|
expect(find.text('Claude'), findsOneWidget);
|
|
|
|
|
expect(find.text('With your own key'), findsNothing);
|
|
|
|
|
expect(find.text('Add an assistant with your own key'), findsNothing);
|
|
|
|
|
});
|
|
|
|
|
}
|