fix(calendar): OAuth 토큰 만료/철회 시 복구 안내 에러로 번역 (v2.2.219)

/task·/meet 등록에서 'Token has been expired or revoked.' raw 구글 에러만
노출되어 사용자가 복구 방법을 알 수 없던 문제. getFreshAccessToken 이
expired/revoked/invalid_grant 를 감지하면 재연결 명령("Astra: Google
Calendar OAuth 연결 (쓰기)")과 근본 원인 안내(OAuth 동의 화면 '테스트'
모드 = 리프레시 토큰 7일 만료, '앱 게시'로 영구화)를 함께 표시.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-11 18:18:22 +09:00
parent c42c66a3fc
commit b540923890
3 changed files with 17 additions and 4 deletions
+2 -2
View File
@@ -1,12 +1,12 @@
{
"name": "astra",
"version": "2.2.218",
"version": "2.2.219",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "astra",
"version": "2.2.218",
"version": "2.2.219",
"license": "MIT",
"dependencies": {
"@lmstudio/sdk": "^1.5.0",
+1 -1
View File
@@ -2,7 +2,7 @@
"name": "astra",
"displayName": "Astra",
"description": "The personal intelligence layer for Antigravity and VS Code. A private cognitive partner for deep project context, memory, and proactive strategic decision-making.",
"version": "2.2.218",
"version": "2.2.219",
"publisher": "g1nation",
"license": "MIT",
"icon": "assets/icon.png",
+14 -1
View File
@@ -196,7 +196,20 @@ export async function getFreshAccessToken(
return { ok: true, accessToken: cfg.accessToken };
}
const r = await refreshAccessToken(cfg.clientId, cfg.clientSecret, cfg.refreshToken);
if (!r.ok) return { ok: false, error: r.error };
if (!r.ok) {
// 리프레시 토큰 자체가 만료/철회된 경우 — raw 구글 에러("Token has been
// expired or revoked.")만 보여주면 사용자가 복구 방법을 알 수 없다.
// 재연결 명령 + 흔한 근본 원인(OAuth 동의 화면 '테스트' 모드 = 7일 만료) 안내.
if (/expired|revoked|invalid_grant/i.test(r.error)) {
return {
ok: false,
error: 'Google OAuth 토큰이 만료/철회되었습니다 — "Astra: Google Calendar OAuth 연결 (쓰기)" 명령으로 재연결하세요. '
+ "(자주 반복되면: Google Cloud Console → OAuth 동의 화면이 '테스트' 모드인지 확인 — 테스트 모드는 리프레시 토큰이 7일마다 만료되며, '앱 게시'로 프로덕션 전환 시 만료되지 않습니다.) "
+ `원인: ${r.error}`,
};
}
return { ok: false, error: r.error };
}
await writeCalendarConfig(context, { accessToken: r.accessToken, accessTokenExpiresAt: r.expiresAt });
return { ok: true, accessToken: r.accessToken };
}