fix(daily-briefing): add calendar/tasks scope re-auth guidance
This commit is contained in:
@@ -65,6 +65,18 @@ automation:
|
||||
3. Confirm backup cron and daily briefing cron schedules match operator expectations.
|
||||
4. If using MinIO ingestion, confirm extractor dependencies via doctor output (`MinIO ingest extractors`).
|
||||
|
||||
## Troubleshooting: Daily Briefing Calendar/Tasks Blocked
|
||||
|
||||
If the daily briefing reports Calendar or Tasks as blocked with insufficient permissions, re-run OAuth for both services to refresh scopes/tokens:
|
||||
|
||||
1. `flynn gcal-auth`
|
||||
2. `flynn gtasks-auth`
|
||||
|
||||
Expected scopes after re-auth:
|
||||
|
||||
- Calendar: `https://www.googleapis.com/auth/calendar.readonly`
|
||||
- Tasks: `https://www.googleapis.com/auth/tasks.readonly`
|
||||
|
||||
## Notes
|
||||
|
||||
- Heartbeat notification noise is controlled by `automation.heartbeat.notify_cooldown` (default `30m`).
|
||||
|
||||
+18
-2
@@ -1,8 +1,23 @@
|
||||
{
|
||||
"version": "1.0",
|
||||
"updated_at": "2026-02-23",
|
||||
"updated_at": "2026-02-24",
|
||||
"description": "Tracks the status of all Flynn plans and implementation phases",
|
||||
"plans": {
|
||||
"daily-briefing-google-scope-remediation": {
|
||||
"status": "completed",
|
||||
"date": "2026-02-24",
|
||||
"updated": "2026-02-24",
|
||||
"summary": "Improved Google Calendar and Google Tasks tool error handling so insufficient-scope failures now include explicit re-auth commands (`flynn gcal-auth` / `flynn gtasks-auth`) instead of vague permission errors. Added regression tests and operator runbook troubleshooting steps for daily briefing blockers.",
|
||||
"files_modified": [
|
||||
"src/tools/builtin/gcal.ts",
|
||||
"src/tools/builtin/gtasks.ts",
|
||||
"src/tools/builtin/gcal.test.ts",
|
||||
"src/tools/builtin/gtasks.test.ts",
|
||||
"docs/operations/OPERATOR_PACK.md",
|
||||
"docs/plans/state.json"
|
||||
],
|
||||
"test_status": "pnpm test:run src/tools/builtin/gcal.test.ts src/tools/builtin/gtasks.test.ts src/tools/builtin/gmail.test.ts + pnpm typecheck passing"
|
||||
},
|
||||
"council-tool-timeout-override": {
|
||||
"status": "completed",
|
||||
"date": "2026-02-23",
|
||||
@@ -6355,7 +6370,7 @@
|
||||
}
|
||||
},
|
||||
"overall_progress": {
|
||||
"total_test_count": 1967,
|
||||
"total_test_count": 1971,
|
||||
"all_tests_passing": true,
|
||||
"p0_completion": "3/3 (100%)",
|
||||
"p1_completion": "4/4 (100%)",
|
||||
@@ -6377,6 +6392,7 @@
|
||||
"gmail_filter_creation": "completed — gmail.filter.create tool added with criteria/action validation; gmail-auth requests explicit gmail.settings.basic + gmail.readonly scopes for filter creation and inbox reads",
|
||||
"toolloop_action_intent_recovery": "completed — when a model claims it will execute a tool but emits no tool call, NativeAgent now issues one internal nudge and continues the same turn to execute tools or produce a concrete blocker",
|
||||
"toolloop_execution_claim_recovery": "completed — when a model claims a known tool already succeeded/failed without emitting a tool call, NativeAgent now nudges once and retries the same turn before returning text",
|
||||
"daily_briefing_google_scope_remediation": "completed — calendar.* and tasks.* now append explicit re-auth guidance (`flynn gcal-auth` / `flynn gtasks-auth`) for insufficient-scope errors, and operator runbook includes remediation steps",
|
||||
"council_tool_timeout_override": "completed — ToolExecutor supports per-tool timeout overrides and council.run now uses a 180s timeout to avoid false 30s council timeouts in the tool loop",
|
||||
"minimal_tui_multiline_paste_mode": "completed — minimal TUI now supports `/paste`/`/multiline` multiline compose mode ending with single '.' line, preventing newline truncation for pasted prompts",
|
||||
"native_audio_support": "completed — smart routing for native audio (Gemini/OpenAI/GitHub) vs Whisper transcription fallback, plus 2026-02-23 arg hydration hardening, tool.args_rewritten audit metric, transient fetch retry/timeout hardening, localhost->127.0.0.1 fallback for transcription endpoint connectivity, and whisper docker-compose entrypoint arg fix for port 18801",
|
||||
|
||||
@@ -185,6 +185,19 @@ describe('calendar.today', () => {
|
||||
|
||||
expect(result.success).toBe(false);
|
||||
expect(result.error).toContain('API quota exceeded');
|
||||
expect(result.error).not.toContain('flynn gcal-auth');
|
||||
});
|
||||
|
||||
it('surfaces re-auth hint for insufficient scopes', async () => {
|
||||
setupValidAuth();
|
||||
mockEventsList.mockRejectedValue(new Error('Request had insufficient authentication scopes.'));
|
||||
|
||||
const [todayTool] = createGcalTools(testConfig);
|
||||
const result = await todayTool.execute({});
|
||||
|
||||
expect(result.success).toBe(false);
|
||||
expect(result.error).toContain('insufficient authentication scopes');
|
||||
expect(result.error).toContain('flynn gcal-auth');
|
||||
});
|
||||
|
||||
it('respects calendarId parameter', async () => {
|
||||
@@ -326,6 +339,7 @@ describe('calendar.search', () => {
|
||||
|
||||
expect(result.success).toBe(false);
|
||||
expect(result.error).toContain('API quota exceeded');
|
||||
expect(result.error).not.toContain('flynn gcal-auth');
|
||||
});
|
||||
|
||||
it('formats events with all fields', async () => {
|
||||
|
||||
@@ -109,6 +109,20 @@ function formatEvents(events: EventSummary[]): string {
|
||||
.join('\n\n');
|
||||
}
|
||||
|
||||
function parseErrorMessage(error: unknown): string {
|
||||
if (error instanceof Error) {
|
||||
return error.message;
|
||||
}
|
||||
return String(error);
|
||||
}
|
||||
|
||||
function formatToolError(message: string): string {
|
||||
const needsScopeHint = /insufficient.*scope|insufficientPermissions|Request had insufficient authentication scopes/i.test(message);
|
||||
return needsScopeHint
|
||||
? `${message}. Re-run "flynn gcal-auth" to grant Google Calendar read permissions.`
|
||||
: message;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates Google Calendar query tools bound to the given GcalConfig.
|
||||
* Tools create their own OAuth2 client per invocation.
|
||||
@@ -150,10 +164,11 @@ export function createGcalTools(config: NonNullable<GcalConfig>): Tool[] {
|
||||
output: formatEvents(events),
|
||||
};
|
||||
} catch (error) {
|
||||
const message = parseErrorMessage(error);
|
||||
return {
|
||||
success: false,
|
||||
output: '',
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
error: formatToolError(message),
|
||||
};
|
||||
}
|
||||
},
|
||||
@@ -211,10 +226,11 @@ export function createGcalTools(config: NonNullable<GcalConfig>): Tool[] {
|
||||
output: formatEvents(events),
|
||||
};
|
||||
} catch (error) {
|
||||
const message = parseErrorMessage(error);
|
||||
return {
|
||||
success: false,
|
||||
output: '',
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
error: formatToolError(message),
|
||||
};
|
||||
}
|
||||
},
|
||||
@@ -268,10 +284,11 @@ export function createGcalTools(config: NonNullable<GcalConfig>): Tool[] {
|
||||
output: formatEvents(events),
|
||||
};
|
||||
} catch (error) {
|
||||
const message = parseErrorMessage(error);
|
||||
return {
|
||||
success: false,
|
||||
output: '',
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
error: formatToolError(message),
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
@@ -171,6 +171,19 @@ describe('tasks.lists', () => {
|
||||
|
||||
expect(result.success).toBe(false);
|
||||
expect(result.error).toContain('API quota exceeded');
|
||||
expect(result.error).not.toContain('flynn gtasks-auth');
|
||||
});
|
||||
|
||||
it('surfaces re-auth hint for insufficient scopes', async () => {
|
||||
setupValidAuth();
|
||||
mockTasklistsList.mockRejectedValue(new Error('Request had insufficient authentication scopes.'));
|
||||
|
||||
const [listsTool] = createGtasksTools(testConfig);
|
||||
const result = await listsTool.execute({});
|
||||
|
||||
expect(result.success).toBe(false);
|
||||
expect(result.error).toContain('insufficient authentication scopes');
|
||||
expect(result.error).toContain('flynn gtasks-auth');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -258,6 +271,7 @@ describe('tasks.list', () => {
|
||||
|
||||
expect(result.success).toBe(false);
|
||||
expect(result.error).toContain('Not Found');
|
||||
expect(result.error).not.toContain('flynn gtasks-auth');
|
||||
});
|
||||
|
||||
it('respects maxResults parameter', async () => {
|
||||
|
||||
@@ -94,6 +94,20 @@ function formatTasks(tasks: TaskSummary[]): string {
|
||||
.join('\n\n');
|
||||
}
|
||||
|
||||
function parseErrorMessage(error: unknown): string {
|
||||
if (error instanceof Error) {
|
||||
return error.message;
|
||||
}
|
||||
return String(error);
|
||||
}
|
||||
|
||||
function formatToolError(message: string): string {
|
||||
const needsScopeHint = /insufficient.*scope|insufficientPermissions|Request had insufficient authentication scopes/i.test(message);
|
||||
return needsScopeHint
|
||||
? `${message}. Re-run "flynn gtasks-auth" to grant Google Tasks read permissions.`
|
||||
: message;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates Google Tasks read-only tools bound to the given GtasksConfig.
|
||||
* Tools create their own OAuth2 client per invocation.
|
||||
@@ -137,10 +151,11 @@ export function createGtasksTools(config: NonNullable<GtasksConfig>): Tool[] {
|
||||
output: formatTaskLists(lists),
|
||||
};
|
||||
} catch (error) {
|
||||
const message = parseErrorMessage(error);
|
||||
return {
|
||||
success: false,
|
||||
output: '',
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
error: formatToolError(message),
|
||||
};
|
||||
}
|
||||
},
|
||||
@@ -204,10 +219,11 @@ export function createGtasksTools(config: NonNullable<GtasksConfig>): Tool[] {
|
||||
output: formatTasks(taskList),
|
||||
};
|
||||
} catch (error) {
|
||||
const message = parseErrorMessage(error);
|
||||
return {
|
||||
success: false,
|
||||
output: '',
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
error: formatToolError(message),
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user