@cartanova/qgrid-cli 2.9.2 → 2.9.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bundle/dist/application/qgrid/expired-token-reminder.js +6 -5
- package/bundle/dist/application/qgrid/qgrid-image-generation.js +12 -3
- package/bundle/dist/application/qgrid/qgrid-response-format.js +8 -6
- package/bundle/dist/application/qgrid/qgrid-run-lifecycle.js +2 -2
- package/bundle/dist/application/qgrid/qgrid.frame.js +4 -3
- package/bundle/dist/application/qgrid/qgrid.types.js +31 -4
- package/bundle/dist/application/qgrid/token-death.js +6 -22
- package/bundle/dist/application/qgrid/token-subscriber.js +2 -1
- package/bundle/dist/application/qgrid/tool-emulation.js +3 -2
- package/bundle/dist/application/setting/setting.model.js +8 -2
- package/bundle/dist/application/token/token.model.js +22 -17
- package/bundle/dist/utils/providers/common/model-cost.js +21 -13
- package/bundle/dist/utils/providers/openai/openai-backend-protocol.js +1 -1
- package/bundle/dist/utils/providers/openai/openai-direct-client.js +39 -1
- package/bundle/dist/utils/providers/openai/openai-dispatcher.js +41 -20
- package/bundle/dist/utils/providers/openai/openai-image-generation.js +145 -0
- package/bundle/dist/utils/providers/openai/openai-quota.js +11 -3
- package/bundle/dist/utils/providers/openai/openai-refresh.js +7 -5
- package/bundle/dist/utils/slack-notify.js +8 -4
- package/bundle/src/application/qgrid/expired-token-reminder.test.ts +6 -1
- package/bundle/src/application/qgrid/expired-token-reminder.ts +10 -4
- package/bundle/src/application/qgrid/qgrid-image-generation.test.ts +53 -0
- package/bundle/src/application/qgrid/qgrid-image-generation.ts +32 -2
- package/bundle/src/application/qgrid/qgrid-response-format.test.ts +26 -0
- package/bundle/src/application/qgrid/qgrid-response-format.ts +21 -13
- package/bundle/src/application/qgrid/qgrid-run-lifecycle.test.ts +32 -0
- package/bundle/src/application/qgrid/qgrid-run-lifecycle.ts +1 -1
- package/bundle/src/application/qgrid/qgrid.frame.test.ts +20 -0
- package/bundle/src/application/qgrid/qgrid.frame.ts +3 -2
- package/bundle/src/application/qgrid/qgrid.types.ts +32 -0
- package/bundle/src/application/qgrid/token-death.test.ts +11 -10
- package/bundle/src/application/qgrid/token-death.ts +9 -21
- package/bundle/src/application/qgrid/token-subscriber.test.ts +5 -1
- package/bundle/src/application/qgrid/token-subscriber.ts +1 -0
- package/bundle/src/application/qgrid/tool-emulation.test.ts +18 -0
- package/bundle/src/application/qgrid/tool-emulation.ts +3 -0
- package/bundle/src/application/setting/setting.model.test.ts +20 -0
- package/bundle/src/application/setting/setting.model.ts +10 -2
- package/bundle/src/application/sonamu.generated.http +4 -2
- package/bundle/src/application/token/token.model.test.ts +24 -5
- package/bundle/src/application/token/token.model.ts +30 -23
- package/bundle/src/utils/providers/common/effort.test.ts +8 -0
- package/bundle/src/utils/providers/common/model-cost.test.ts +25 -12
- package/bundle/src/utils/providers/common/model-cost.ts +28 -14
- package/bundle/src/utils/providers/common/provider-dispatcher.ts +5 -1
- package/bundle/src/utils/providers/openai/openai-backend-protocol.ts +9 -1
- package/bundle/src/utils/providers/openai/openai-direct-client.ts +48 -1
- package/bundle/src/utils/providers/openai/openai-dispatcher.test.ts +137 -2
- package/bundle/src/utils/providers/openai/openai-dispatcher.ts +44 -18
- package/bundle/src/utils/providers/openai/openai-image-generation.test.ts +135 -0
- package/bundle/src/utils/providers/openai/openai-image-generation.ts +177 -0
- package/bundle/src/utils/providers/openai/openai-quota.test.ts +32 -0
- package/bundle/src/utils/providers/openai/openai-quota.ts +15 -8
- package/bundle/src/utils/providers/openai/openai-refresh.test.ts +18 -0
- package/bundle/src/utils/providers/openai/openai-refresh.ts +11 -4
- package/bundle/src/utils/slack-notify.test.ts +14 -0
- package/bundle/src/utils/slack-notify.ts +19 -3
- package/bundle/web-dist/client/assets/{index-DH1n8kgG.js → index-BcCytvFf.js} +3 -3
- package/bundle/web-dist/client/assets/{logs-D36O-NET.js → logs-BkOilk5x.js} +1 -1
- package/bundle/web-dist/client/assets/{monit-DhZAaz9K.js → monit-8oCSLCdL.js} +1 -1
- package/bundle/web-dist/client/assets/{routes-C7q1CsSv.js → routes-DFIF4IoW.js} +2 -2
- package/bundle/web-dist/client/assets/{settings-M_jfoIAe.js → settings-nUI-DR3U.js} +1 -1
- package/bundle/web-dist/client/assets/{show-CORjMGQs.js → show-Bzx6Xkoc.js} +1 -1
- package/bundle/web-dist/client/assets/{tokens-C7-j4VXD.js → tokens-DFWCL7Ts.js} +1 -1
- package/bundle/web-dist/client/assets/{use-oauth-login-flow-Fi62xbBc.js → use-oauth-login-flow-B5MAjpVc.js} +1 -1
- package/bundle/web-dist/client/index.html +1 -1
- package/bundle/web-dist/server/assets/{routes-CxMHXfdH.js → routes-B408iqh1.js} +29 -14
- package/bundle/web-dist/server/entry-server.generated.js +3 -2
- package/package.json +2 -1
- package/skills/qgrid/SKILL.md +2 -1
- package/skills/qgrid/references/ai-sdk-provider-contract.md +2 -2
- package/skills/qgrid/references/decision-rationale.md +2 -2
- package/skills/qgrid/references/openai-codex-runtime.md +79 -0
- package/skills/qgrid/references/provider-runtime-differences.md +1 -1
- package/skills/qgrid/references/request-log-run-lifecycle.md +1 -1
- package/skills/qgrid/references/sonamu-api-web-flow.md +1 -1
- package/skills/qgrid/references/token-auth-quota-lifecycle.md +4 -3
|
@@ -12,6 +12,77 @@ Use this reference before changing OpenAI transport, concurrency, routing, promp
|
|
|
12
12
|
- Direct quota lookup: `openai-quota.ts`.
|
|
13
13
|
- Provider integration and history: `packages/api/src/application/qgrid/qgrid.dispatcher.ts`, `conv-routing.ts`.
|
|
14
14
|
|
|
15
|
+
## Adding an OpenAI model
|
|
16
|
+
|
|
17
|
+
Use this checklist whenever a model is added. A public API release does not by itself prove
|
|
18
|
+
availability or identical limits on qgrid's ChatGPT-subscription route.
|
|
19
|
+
|
|
20
|
+
1. Fetch the official model page and pricing table. Record the exact ID, Standard input/output,
|
|
21
|
+
cache-read and cache-write prices, long-context threshold and multipliers, modalities,
|
|
22
|
+
structured-output support, and reasoning restrictions. Do not infer pricing from a related model.
|
|
23
|
+
2. Inspect the current Codex model catalog (`~/.codex/models_cache.json`) for the exact slug,
|
|
24
|
+
`supported_reasoning_levels`, and `context_window`. Separate public API specifications from
|
|
25
|
+
subscription catalog values. Catalog availability is not a successful generation test.
|
|
26
|
+
3. Add pricing and the subscription `maxEffort` to `OPENAI_COSTS` in
|
|
27
|
+
`packages/api/src/utils/providers/common/model-cost.ts`. Preserve existing historical model
|
|
28
|
+
rows. The shared calculator also prices request logs; no separate dashboard price table is needed.
|
|
29
|
+
4. Add `openai/<slug>` to `QgridSupportedModel` in `packages/ai-sdk/src/index.types.ts` and
|
|
30
|
+
`MODEL_PRESET_GROUPS` in `packages/web/src/components/qgrid/ChatWidget.tsx`. The web package
|
|
31
|
+
does not import the SDK model union. Keep existing defaults unless a default change is requested.
|
|
32
|
+
5. Verify model ID and effort propagation through SDK generation/streaming, provider dispatch,
|
|
33
|
+
Responses construction, and returned model metadata. New models use the existing OpenAI
|
|
34
|
+
route; do not add a second transport or enable built-in tools automatically.
|
|
35
|
+
6. Update the root and AI SDK README model lists, type snippets, capability/pricing tables,
|
|
36
|
+
and affected skill contracts. Sync the canonical skill to its checked mirror.
|
|
37
|
+
7. Test prices, cache accounting, the long-context boundary, reasoning levels, and SDK request
|
|
38
|
+
propagation. Run relevant provider tests, API/web/SDK type checks, SDK build, and `mise run check`.
|
|
39
|
+
Report mocked tests separately from any live smoke test. Model registration does not authorize
|
|
40
|
+
a release, version bump, deployment, or remote migration.
|
|
41
|
+
|
|
42
|
+
### GPT-6 Astra
|
|
43
|
+
|
|
44
|
+
`openai/gpt-6-astra` uses the existing text generation, streaming and structured-output paths.
|
|
45
|
+
The model supports image input, but qgrid keeps its existing SDK restriction: image parts are
|
|
46
|
+
forwarded only for opt-in `imageGeneration` reference-image requests, not general vision chat.
|
|
47
|
+
Standard API-equivalent prices per million tokens are input $10, cached input $1, cache write
|
|
48
|
+
$12.50, and output $50. Above 272,000 input tokens, the full request uses 2x input/cache rates and
|
|
49
|
+
1.5x output rates. These are qgrid's cost estimates, not subscription billing amounts or Fast-tier prices.
|
|
50
|
+
|
|
51
|
+
Sources: [model specification](https://developers.openai.com/api/docs/models/gpt-6-astra) and
|
|
52
|
+
[pricing](https://developers.openai.com/api/docs/pricing), checked 2026-09-07. The public model page
|
|
53
|
+
lists a 1,050,000-token context window, 922,000 maximum input and 128,000 maximum output, with effort
|
|
54
|
+
through `max`. The Codex subscription catalog checked on the same date lists `context_window=272000`
|
|
55
|
+
and `low | medium | high | xhigh | max | ultra`; qgrid therefore permits `ultra` on this route.
|
|
56
|
+
Do not present the public 1.05M context as a verified subscription-route limit.
|
|
57
|
+
|
|
58
|
+
## Existing OpenAI model pricing and limits
|
|
59
|
+
|
|
60
|
+
Checked 2026-09-07 against the official [Standard pricing table](https://developers.openai.com/api/docs/pricing)
|
|
61
|
+
and the local Codex subscription catalog. Prices are USD per million tokens:
|
|
62
|
+
|
|
63
|
+
| Model | Input | Cached input | Cache write | Output | Codex context | Maximum Codex effort |
|
|
64
|
+
|---|---:|---:|---:|---:|---:|---|
|
|
65
|
+
| GPT-5.6 Sol | 4 | 0.40 | 5 | 20 | 272K | ultra |
|
|
66
|
+
| GPT-5.6 Terra | 2 | 0.20 | 2.50 | 12 | 272K | ultra |
|
|
67
|
+
| GPT-5.6 Luna | 0.20 | 0.02 | 0.25 | 1.20 | 272K | max |
|
|
68
|
+
| GPT-5.5 | 5 | 0.50 | Not published | 30 | 272K | xhigh |
|
|
69
|
+
|
|
70
|
+
GPT-5.3-Codex-Spark has a 128K catalog context and effort through `xhigh`; its token pricing
|
|
71
|
+
remains an explicitly documented generic estimate. Retired model pricing rows remain for legacy logs.
|
|
72
|
+
The catalog does not establish a maximum output size. GPT-5.6 public API specifications list 1.05M
|
|
73
|
+
context, 922K maximum input and 128K maximum output; do not conflate them with the subscription limits.
|
|
74
|
+
Codex default effort is `low` for Sol, `medium` for Terra/Luna/GPT-5.5, and `high` for Spark.
|
|
75
|
+
The SDK default remains `low` regardless of the backend default.
|
|
76
|
+
|
|
77
|
+
[Sol's model page](https://developers.openai.com/api/docs/models/gpt-5.6-sol) guarantees promotional
|
|
78
|
+
pricing at least through 2026-11-21. That is not an announced expiration date. Keep the latest verified
|
|
79
|
+
rates until another official rate is published; never guess a future reversion. Above 272K input,
|
|
80
|
+
GPT-5.6 and GPT-5.5 apply 2x input/cache and 1.5x output to the full request.
|
|
81
|
+
|
|
82
|
+
Changing the price table affects new calculations and legacy estimates that resolve rates on read.
|
|
83
|
+
It does not rewrite persisted request-log costs or backfill historical records. Report a historical
|
|
84
|
+
recalculation as a separate data change; do not silently include it in a pricing update.
|
|
85
|
+
|
|
15
86
|
## Private backend boundary
|
|
16
87
|
|
|
17
88
|
OpenAI requests go directly to `https://chatgpt.com/backend-api/codex/responses` with an HTTPS `POST`. The response is an SSE stream. Qgrid sends the subscription bearer token, ChatGPT account id, content negotiation fields, and Codex CLI `originator` and `User-Agent` identity headers.
|
|
@@ -38,6 +109,8 @@ Selection picks among active, quota-eligible tokens: a cache-affinity-preferred
|
|
|
38
109
|
|
|
39
110
|
Quota lookup failures fail open. A successful lookup over a token's configured threshold excludes that token until the cached usage is refreshed.
|
|
40
111
|
|
|
112
|
+
A quota HTTP 401 uses the same per-token refresh path as generation, then retries the GET once. Explicit permanent refresh error codes (string or nested `error.code`) mark the credentials as requiring re-login and deactivate the token through the shared auth-death path. Other lookup failures do not prove an expired login. In-flight quota lookups and cached results are scoped to a metadata generation; a subscriber credential update takes precedence over an older refresh result.
|
|
113
|
+
|
|
41
114
|
## Full-history replay and cache affinity
|
|
42
115
|
|
|
43
116
|
Qgrid no longer retains an OpenAI provider thread. It sends the full conversation history on every turn. The AI SDK derives a model-scoped opaque value from `sessionKey`; the server validates and forwards it as `prompt_cache_key`.
|
|
@@ -72,3 +145,9 @@ Quota is fetched directly from `https://chatgpt.com/backend-api/wham/usage` with
|
|
|
72
145
|
OpenAI usage reports total input, cached input, output, reasoning, and total tokens for the request. Preserve cached input as a subset of input; do not add it to input again when computing cache-hit rate.
|
|
73
146
|
|
|
74
147
|
Image generation is opt-in and non-stream at the AI SDK interface. The backend still delivers its response through SSE. Returned base64 images become AI SDK PNG file parts. Recorded image cost remains an estimate based on qgrid's configured public image price table because this route does not expose exact image-tool billing.
|
|
148
|
+
|
|
149
|
+
### Transparent image route
|
|
150
|
+
|
|
151
|
+
Only `imageGenerationOptions.background: "transparent"` switches from hosted Responses to Codex `images/generations` or `images/edits`. The same selected subscription token, cancellation deadline, in-flight tracking, and one definitive 401 refresh apply. Use JSON content negotiation; do not reuse the Responses SSE Accept header. Never replay an ambiguous generation or fall back to opaque output/IS-Net. This route runs `gpt-image-2` directly, so actual model metadata differs from the requested driver ID and ordinary driver usage stays zero. Image response usage and actual settings are preserved on `generation`; usage belongs to the first output only. Costs remain explicitly estimated from public prices.
|
|
152
|
+
|
|
153
|
+
The image request adapter preserves text/system context and reference images (maximum five), rejects unsupported structured/tool requests before generation, and validates PNG contents with pngjs. Both a fully transparent pixel and visible content are required. Invalid, opaque, empty, or oversized results fail. Returned dimensions come from the decoded PNG; qgrid never silently resizes. A 2026-09-08 live request returned 1254x1254/medium for requested 1024x1024/high. Do not promise exact size/quality enforcement. See `openai-image-generation.ts` and opt-in `transparent`/`transparent-edit` cases in the image smoke script.
|
|
@@ -12,7 +12,7 @@ Use this reference when comparing OpenAI and Anthropic behavior, debugging provi
|
|
|
12
12
|
| Token selection | Smooth weighted RR over quota-eligible tokens; valid affinity may prefer its token | Smooth weighted RR over quota-eligible tokens per request |
|
|
13
13
|
| Model routing | `openai/*`; qgrid strips provider prefix before provider call | `anthropic/*`; provider canonicalizes model and strips prefix/`[1m]` |
|
|
14
14
|
| Prefix-less models | Fallback not implemented | Fallback not implemented |
|
|
15
|
-
| Effort vocabulary | Codex catalog levels `low`..`ultra` (`
|
|
15
|
+
| Effort vocabulary | Codex catalog levels `low`..`ultra` (Astra/Sol/Terra through `ultra`, Luna through `max`); public-API `none`/`minimal` absent | Claude Code `--effort` levels `low`..`max`; per-model caps handled by Claude Code |
|
|
16
16
|
| Unsupported effort | Server drops it silently before the request (`resolveOpenAIEffort`), backend default applies | Server drops it silently (`resolveAnthropicEffort`), qgrid default `low` applies |
|
|
17
17
|
| Thread/session | No provider thread retained | Fresh Claude `--session-id` per request |
|
|
18
18
|
| `sessionKey` | AI SDK derives and replays opaque `prompt_cache_key` affinity | AI SDK intentionally disables storage/replay |
|
|
@@ -78,7 +78,7 @@ OpenAI uses the completed Responses event's per-request usage fields.
|
|
|
78
78
|
|
|
79
79
|
Anthropic normalizes native mutually exclusive categories by summing input + cache creation + cache read into total input. Prefer positive Claude Code `total_cost_usd`; otherwise calculate from the actual serving model and TTL split.
|
|
80
80
|
|
|
81
|
-
For image requests, keep `cost_usd` as the Codex driver model token cost.
|
|
81
|
+
For image requests, keep `cost_usd` as the Codex driver model token cost. Hosted image output costs use quality/size assumptions because image-tool usage is not exposed. Transparent standalone Images requests run no driver and report zero driver usage/cost; their image usage and actual settings are preserved as `observedGeneration` alongside `requestedOptions` in the synthetic tool log. Their separate image cost uses reported usage with an explicitly conservative public-price estimate. Treat `image_cost_usd` as a price-table estimate that may be inaccurate if Codex changes its underlying image accounting.
|
|
82
82
|
|
|
83
83
|
Reference images for image generation are not stored in `request_logs.user_prompt`; that field remains the text prompt. For inspection in the detail view, qgrid stores reference image metadata/base64 in the synthetic `image_generation` step's `tool_args.inputImages`. If Codex returns multiple image outputs, store `inputImages` only on the first synthetic image tool step so multi-output logs do not duplicate the same input payload on every output.
|
|
84
84
|
|
|
@@ -64,7 +64,7 @@ Tool contract visibility: the detail page renders a Tools panel between Response
|
|
|
64
64
|
|
|
65
65
|
Chat token targeting: the dashboard chat panel can pin a conversation to one account. It defaults to automatic distribution and lists only active tokens whose provider matches the selected model, sending the choice as `tokenName` so the request lands on exactly that account with no fallback. Changing either the model or the token resets the existing conversation coordinates, because a thread's prompt cache belongs to the account that built it. A selection that disappears — deactivated or deleted — is cleared automatically, but the selection is preserved while the token list is still loading so a slow fetch does not silently reset the user's choice.
|
|
66
66
|
|
|
67
|
-
Chat model presets: the model `<select>` lists the served subset of `QgridSupportedModel` grouped by provider (`MODEL_PRESET_GROUPS` in `ChatWidget.tsx`), newest first, and defaults to `anthropic/claude-fable-5-1`. Ids that the ChatGPT-subscription Codex route no longer serves (`gpt-5.4` and `gpt-5.4-mini` retired 2026-08-31 with `gpt-5.6-terra` / `gpt-5.6-luna` as replacements; `gpt-5.2` and `gpt-5.3-codex` removed earlier) and the catalog-less `claude-sonnet-4-7` stay in the SDK type but are excluded here. web does not depend on ai-sdk, so a new model must be added in both places.
|
|
67
|
+
Chat model presets: the model `<select>` lists the served subset of `QgridSupportedModel` grouped by provider (`MODEL_PRESET_GROUPS` in `ChatWidget.tsx`), newest first, and defaults to `anthropic/claude-fable-5-1`. Ids that the ChatGPT-subscription Codex route no longer serves (`gpt-5.4` and `gpt-5.4-mini` retired 2026-08-31 with `gpt-5.6-terra` / `gpt-5.6-luna` as replacements; `gpt-5.2` and `gpt-5.3-codex` removed earlier) and the catalog-less `claude-sonnet-4-7` stay in the SDK type but are excluded here. web does not depend on ai-sdk, so a new model must be added in both places. `openai/gpt-6-astra` is included in the OpenAI group; adding it does not change the default model.
|
|
68
68
|
|
|
69
69
|
The monit tab's vitals strip shows per-provider in-flight counts, an in-flight sparkline, token chips with quota gauges (fed by the dispatcher's 60-second rate-limits cache; `resetsAt` arrives as ms epoch), and a 1-hour per-provider request/error/cache-hit stats line polled separately every 30 seconds.
|
|
70
70
|
|
|
@@ -204,13 +204,14 @@ When a refresh fails in a way that only re-login can fix, qgrid removes the toke
|
|
|
204
204
|
|
|
205
205
|
- Detection lives at the existing refresh failure points, not a background scanner. OpenAI classifies on the error code (`refresh_token_expired`, `refresh_token_reused`, `refresh_token_invalidated`) regardless of HTTP status; Anthropic treats refresh 400/401 as auth-dead.
|
|
206
206
|
- Before deactivating, qgrid locks the token row and confirms the full credentials used by the failed attempt still match the stored credentials. A successful concurrent refresh therefore wins without a stale failure killing the rotated login. `QgridFrame.refreshToken` also dedups concurrent refreshes per token.
|
|
207
|
-
- Auth death is one conditional `reauth_required=false → true` transaction. Provider-scoped advisory locking serializes the last-active decision, and the update
|
|
207
|
+
- Auth death is one conditional `reauth_required=false → true` transaction. Provider-scoped advisory locking serializes the last-active decision, and the update always sets `active=false`, including for the last active token. The process whose update affects a row is the only one that sends the Slack death notification, so a shared database produces exactly one alert per event. Manual active toggles never set `reauth_required`, and an expired inactive token cannot be reactivated without completing OAuth.
|
|
208
208
|
- A successful refresh clears a concurrent stale `reauth_required` mark but preserves `active`, so it never overrides an operator's manual inactive choice.
|
|
209
|
-
- The provider's last active token is
|
|
209
|
+
- The provider's last active token is also deactivated. Its ordinary `세션 만료` notification is urgent and explains that no usable tokens remain; retaining a confirmed expired login cannot keep the provider usable. The persisted flag gates repeat alerts across instances. Startup and periodic subscriber reconciliation deactivate legacy rows with both `reauth_required=true` and `active=true` before loading the routing pool.
|
|
210
|
+
- The dashboard shows `Session expired` only when `reauth_required=true`, including inactive tokens. Usage errors such as a one-off 401 or a transient refresh failure show `Unavailable`; error-message text is not a second expiry classifier.
|
|
210
211
|
- The other notification is registration: every completed login sends one alert, whether the account is new or replacing an existing row through the account dedup. Manually toggling a token back to active is not a registration and sends nothing.
|
|
211
212
|
- Quiet hours suppress non-urgent Slack notifications: weekends, plus 20:00–08:00 on weekdays, in `Asia/Seoul`. All three parts are settings (`slack.quietFromHour`, `slack.quietUntilHour`, `slack.notifyOnWeekends`), defaulting to that window. The hour is computed through `Intl` with an explicit timezone rather than `getHours()` — dev0 runs in UTC, where "20:00" would land at 05:00 Korean time. Expiry alerts repeat, so one dropped in quiet hours returns on the first cycle of the next working window. The last-active-token death alert sets `urgent: true` and ignores quiet hours: it means that provider has no usable login, which cannot wait for the next working day.
|
|
212
213
|
- `slack.enabled` exists because holidays cannot be expressed as a recurring rule — substitute holidays, one-off national days, and company closures change every year, so an operator turns notifications off directly instead. It gates the same non-urgent path as quiet hours, which keeps the escape hatch for pool-empty alerts identical in both mechanisms.
|
|
213
|
-
-
|
|
214
|
+
- Background notifications are fail-open: unset `SLACK_BOT_TOKEN`/`SLACK_CHANNEL_ID` is a silent no-op, and send failures (including HTTP 200 with `ok:false`) only warn. Manual reminder sends opt into failure propagation: missing configuration, Slack rejection, and network failure become readable API errors, and a sent count is returned only after successful delivery. Payloads and errors never include the raw provider response body.
|
|
214
215
|
- `expired-token-reminder.ts` repeats the session-expiry alert only for tokens with `reauth_required = true`; manually inactive tokens are not expired sessions. It reuses the same `세션 만료` title rather than inventing a second name — there is one event, so a differently-named alert would read as a separate problem. It runs once at startup and then every `SLACK_EXPIRY_REMINDER_INTERVAL_MINUTES` (unset or 0 disables), batches every expired token into one message, and sends nothing when none require re-login. The startup run matters because a restart is exactly when someone is watching, and waiting a full interval to learn a token is already dead is backwards; the cost is one extra alert per restart — a periodic "all clear" trains people to ignore the channel.
|
|
215
216
|
- Owners are mentioned through `SLACK_USER_MAP` (`tokenName:SlackUserId`) on every token notification, not just the reminder. The map is explicit rather than resolved through `users.list`: Slack handles diverge from token names often enough (`haze`→`haze.lee`, `byeongjun`→`potados`) that auto-matching drops the mention silently, and an alert nobody is tagged in has no one to act on it. Shared accounts are left out of the map on purpose and appear by name only. Each instance sends its own reminder — unlike deactivation there is no state change to gate on, and a few duplicates a day cost less than a locking scheme.
|
|
216
217
|
|