feat(setup): skip redundant operator-pack prompts and add strict-mode docs/tests

This commit is contained in:
William Valentin
2026-02-16 15:27:27 -08:00
parent 7ce58f5966
commit dc1c691ea5
6 changed files with 103 additions and 8 deletions
+13
View File
@@ -6,6 +6,16 @@ All notable changes to Flynn are documented in this file.
### Added
- **Operator Setup Pack + Strict Doctor Mode** -- `flynn setup` Automation now
supports an Operator Pack that preconfigures backup scheduling/notifications,
heartbeat alerts, daily briefing, and MinIO sync with configurable output
channel/peer routing. `flynn doctor --strict` now treats warnings as failures
for CI and preflight gating workflows.
- **Heartbeat Notification Cooldown Config** -- `automation.heartbeat.notify_cooldown`
adds throttling for repeated failure/recovery alerts (default `30m`) to reduce
noisy notifications during unstable periods.
- **Native Audio Support** -- Smart routing for voice messages: audio-capable models
(Gemini, OpenAI, GitHub) receive raw audio directly via `AudioSource` content parts;
non-audio models (Anthropic, Bedrock, Ollama, llama.cpp) get Whisper transcription
@@ -130,6 +140,9 @@ All notable changes to Flynn are documented in this file.
### Fixed
- **Heartbeat Cooldown Parse Resilience** -- Invalid `automation.heartbeat.notify_cooldown`
values no longer throw during alerting; monitor falls back to `30m` and logs a warning.
- **Voice Message Failure Handling** -- Telegram voice/audio messages now send user feedback on
download failures instead of silently dropping. When audio transcription is not configured for
non-audio-capable models, a graceful error message is sent to the user instead of
+29
View File
@@ -771,6 +771,35 @@ Repeated failure/recovery notifications are throttled by `notify_cooldown`.
`flynn setup` now includes an Operator Pack option in Automation that preconfigures scheduled backups, heartbeat alerts, a daily briefing, and a default MinIO sync task, with prompts for output channel/peer routing.
Example Operator Pack output routing:
```yaml
backup:
enabled: true
schedule: "0 2 * * *"
notify:
channel: telegram
peer: "123456789"
automation:
heartbeat:
enabled: true
notify:
channel: telegram
peer: "123456789"
notify_cooldown: "30m"
daily_briefing:
enabled: true
output:
channel: telegram
peer: "123456789"
minio_sync:
enabled: true
notify:
channel: telegram
peer: "123456789"
```
## Gmail Pub/Sub Watcher
Monitor a Gmail inbox and forward new messages into the agent pipeline.
+5 -3
View File
@@ -193,8 +193,9 @@
"status": "completed",
"date": "2026-02-16",
"updated": "2026-02-16",
"summary": "Implemented operator-focused hardening and onboarding polish: added setup Operator Pack flows in both Automation menu and first-run wizard to preconfigure scheduled backups, heartbeat alerts, daily briefing, and default MinIO sync; added operator-pack prompts for output channel/peer routing; added heartbeat notification throttling via `automation.heartbeat.notify_cooldown` (with invalid-value fallback handling); and added `flynn doctor --strict` to treat warnings as failures. Updated docs/default config examples and onboarding tests accordingly.",
"summary": "Implemented operator-focused hardening and onboarding polish: added setup Operator Pack flows in both Automation menu and first-run wizard to preconfigure scheduled backups, heartbeat alerts, daily briefing, and default MinIO sync; added operator-pack prompts for output channel/peer routing with redundant prompt skip when already configured; added heartbeat notification throttling via `automation.heartbeat.notify_cooldown` (with invalid-value fallback handling); and added `flynn doctor --strict` to treat warnings as failures. Added docs/changelog updates and a consolidated regression run record.",
"files_modified": [
"CHANGELOG.md",
"src/cli/setup/config.ts",
"src/cli/setup/config.test.ts",
"src/cli/setup/automation.test.ts",
@@ -203,6 +204,7 @@
"src/cli/setup/integration.test.ts",
"src/cli/setup/summary.ts",
"src/cli/setup/summary.test.ts",
"src/cli/index.test.ts",
"src/automation/heartbeat.ts",
"src/automation/heartbeat.test.ts",
"src/config/schema.ts",
@@ -213,7 +215,7 @@
"README.md",
"docs/plans/state.json"
],
"test_status": "pnpm test:run src/cli/setup/summary.test.ts src/cli/setup/integration.test.ts src/cli/setup/automation.test.ts src/cli/setup/config.test.ts src/automation/heartbeat.test.ts src/config/schema.test.ts src/cli/doctor.test.ts + pnpm typecheck passing"
"test_status": "pnpm test:run src/cli/index.test.ts src/cli/setup/automation.test.ts src/cli/setup/integration.test.ts src/cli/setup/summary.test.ts src/automation/heartbeat.test.ts src/config/schema.test.ts src/cli/doctor.test.ts + pnpm typecheck passing"
},
"backup-session-summary-audit-trail": {
"status": "completed",
@@ -3499,7 +3501,7 @@
}
},
"overall_progress": {
"total_test_count": 1870,
"total_test_count": 1872,
"all_tests_passing": true,
"p0_completion": "3/3 (100%)",
"p1_completion": "4/4 (100%)",
+7
View File
@@ -21,6 +21,13 @@ describe('CLI program', () => {
expect(commandNames).toContain('zai-auth');
});
it('registers doctor strict flag on doctor command', () => {
const program = createProgram();
const doctor = program.commands.find((c) => c.name() === 'doctor');
const strictOption = doctor?.options.find((o) => o.long === '--strict');
expect(strictOption).toBeDefined();
});
it('has version info', () => {
const program = createProgram();
expect(program.version()).toBeDefined();
+30
View File
@@ -127,4 +127,34 @@ describe('setupAutomation', () => {
expect(automation?.heartbeat).toBeUndefined();
expect(automation?.minio_sync).toBeUndefined();
});
it('skips operator-pack reconfiguration prompts when already configured and not reconfigured', async () => {
const builder = new ConfigBuilder();
builder.applyOperatorPack({
outputChannel: 'telegram',
outputPeer: '123',
backupSchedule: '0 2 * * *',
dailyBriefingSchedule: '0 8 * * *',
enableMinioSync: true,
});
const p = makePrompter({
confirms: [
false, // reconfigure operator pack?
false, // enable cron scheduler
false, // enable webhook receiver
false, // configure google services
],
});
await setupAutomation(p, builder);
const config = builder.build() as Record<string, unknown>;
const backup = config.backup as Record<string, unknown>;
const automation = config.automation as Record<string, unknown>;
const briefing = automation.daily_briefing as Record<string, unknown>;
expect(backup.schedule).toBe('0 2 * * *');
expect(briefing.schedule).toBe('0 8 * * *');
});
});
+19 -5
View File
@@ -57,12 +57,26 @@ const GOOGLE_SERVICES: GoogleService[] = [
];
export async function setupAutomation(p: Prompter, builder: ConfigBuilder): Promise<void> {
const enableOperatorPack = await p.confirm(
'Enable operator automation pack (scheduled backups + heartbeat alerts + daily briefing + MinIO sync)?',
false,
const config = builder.build();
const automation = (config.automation ?? {}) as Record<string, unknown>;
const backup = (config.backup ?? {}) as Record<string, unknown>;
const operatorPackConfigured = Boolean(
backup.enabled === true
&& (automation.heartbeat as { enabled?: boolean } | undefined)?.enabled
&& (automation.daily_briefing as { enabled?: boolean } | undefined)?.enabled,
);
if (enableOperatorPack) {
const config = builder.build();
const configureOperatorPack = operatorPackConfigured
? await p.confirm(
'Operator automation pack appears enabled. Reconfigure operator pack settings now?',
false,
)
: await p.confirm(
'Enable operator automation pack (scheduled backups + heartbeat alerts + daily briefing + MinIO sync)?',
false,
);
if (configureOperatorPack) {
const telegramPeer = config.telegram?.allowed_chat_ids?.[0];
const defaultOutputChannel = telegramPeer ? 'telegram' : 'webchat';
const defaultOutputPeer = telegramPeer ? String(telegramPeer) : 'operator';