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);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
|
|
|
|
|
expect(
|
|
|
|
|
find.text(
|
|
|
|
|
"Whoever runs test.example.org doesn't allow own keys here, so the "
|
|
|
|
|
"app doesn't ask these.",
|
|
|
|
|
),
|
|
|
|
|
findsOneWidget,
|
|
|
|
|
);
|
|
|
|
|
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);
|
|
|
|
|
});
|
|
|
|
|
}
|