feat(setup): skip redundant operator-pack prompts and add strict-mode docs/tests
This commit is contained in:
@@ -6,6 +6,16 @@ All notable changes to Flynn are documented in this file.
|
|||||||
|
|
||||||
### Added
|
### 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
|
- **Native Audio Support** -- Smart routing for voice messages: audio-capable models
|
||||||
(Gemini, OpenAI, GitHub) receive raw audio directly via `AudioSource` content parts;
|
(Gemini, OpenAI, GitHub) receive raw audio directly via `AudioSource` content parts;
|
||||||
non-audio models (Anthropic, Bedrock, Ollama, llama.cpp) get Whisper transcription
|
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
|
### 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
|
- **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
|
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
|
non-audio-capable models, a graceful error message is sent to the user instead of
|
||||||
|
|||||||
@@ -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.
|
`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
|
## Gmail Pub/Sub Watcher
|
||||||
|
|
||||||
Monitor a Gmail inbox and forward new messages into the agent pipeline.
|
Monitor a Gmail inbox and forward new messages into the agent pipeline.
|
||||||
|
|||||||
@@ -193,8 +193,9 @@
|
|||||||
"status": "completed",
|
"status": "completed",
|
||||||
"date": "2026-02-16",
|
"date": "2026-02-16",
|
||||||
"updated": "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": [
|
"files_modified": [
|
||||||
|
"CHANGELOG.md",
|
||||||
"src/cli/setup/config.ts",
|
"src/cli/setup/config.ts",
|
||||||
"src/cli/setup/config.test.ts",
|
"src/cli/setup/config.test.ts",
|
||||||
"src/cli/setup/automation.test.ts",
|
"src/cli/setup/automation.test.ts",
|
||||||
@@ -203,6 +204,7 @@
|
|||||||
"src/cli/setup/integration.test.ts",
|
"src/cli/setup/integration.test.ts",
|
||||||
"src/cli/setup/summary.ts",
|
"src/cli/setup/summary.ts",
|
||||||
"src/cli/setup/summary.test.ts",
|
"src/cli/setup/summary.test.ts",
|
||||||
|
"src/cli/index.test.ts",
|
||||||
"src/automation/heartbeat.ts",
|
"src/automation/heartbeat.ts",
|
||||||
"src/automation/heartbeat.test.ts",
|
"src/automation/heartbeat.test.ts",
|
||||||
"src/config/schema.ts",
|
"src/config/schema.ts",
|
||||||
@@ -213,7 +215,7 @@
|
|||||||
"README.md",
|
"README.md",
|
||||||
"docs/plans/state.json"
|
"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": {
|
"backup-session-summary-audit-trail": {
|
||||||
"status": "completed",
|
"status": "completed",
|
||||||
@@ -3499,7 +3501,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"overall_progress": {
|
"overall_progress": {
|
||||||
"total_test_count": 1870,
|
"total_test_count": 1872,
|
||||||
"all_tests_passing": true,
|
"all_tests_passing": true,
|
||||||
"p0_completion": "3/3 (100%)",
|
"p0_completion": "3/3 (100%)",
|
||||||
"p1_completion": "4/4 (100%)",
|
"p1_completion": "4/4 (100%)",
|
||||||
|
|||||||
@@ -21,6 +21,13 @@ describe('CLI program', () => {
|
|||||||
expect(commandNames).toContain('zai-auth');
|
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', () => {
|
it('has version info', () => {
|
||||||
const program = createProgram();
|
const program = createProgram();
|
||||||
expect(program.version()).toBeDefined();
|
expect(program.version()).toBeDefined();
|
||||||
|
|||||||
@@ -127,4 +127,34 @@ describe('setupAutomation', () => {
|
|||||||
expect(automation?.heartbeat).toBeUndefined();
|
expect(automation?.heartbeat).toBeUndefined();
|
||||||
expect(automation?.minio_sync).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 * * *');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -57,12 +57,26 @@ const GOOGLE_SERVICES: GoogleService[] = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
export async function setupAutomation(p: Prompter, builder: ConfigBuilder): Promise<void> {
|
export async function setupAutomation(p: Prompter, builder: ConfigBuilder): Promise<void> {
|
||||||
const enableOperatorPack = await p.confirm(
|
const config = builder.build();
|
||||||
'Enable operator automation pack (scheduled backups + heartbeat alerts + daily briefing + MinIO sync)?',
|
const automation = (config.automation ?? {}) as Record<string, unknown>;
|
||||||
false,
|
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 telegramPeer = config.telegram?.allowed_chat_ids?.[0];
|
||||||
const defaultOutputChannel = telegramPeer ? 'telegram' : 'webchat';
|
const defaultOutputChannel = telegramPeer ? 'telegram' : 'webchat';
|
||||||
const defaultOutputPeer = telegramPeer ? String(telegramPeer) : 'operator';
|
const defaultOutputPeer = telegramPeer ? String(telegramPeer) : 'operator';
|
||||||
|
|||||||
Reference in New Issue
Block a user