@cartanova/qgrid-cli 2.10.10 → 2.11.1
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/_virtual/_rolldown/runtime.js +1 -10
- package/bundle/dist/application/monit/log-buffer.js +12 -6
- package/bundle/dist/application/monit/monit.frame.js +11 -3
- package/bundle/dist/application/monit/monit.types.js +1 -1
- package/bundle/dist/application/qgrid/conv-routing.js +9 -6
- package/bundle/dist/application/qgrid/expired-token-reminder.js +2 -2
- package/bundle/dist/application/qgrid/oauth.js +31 -4
- package/bundle/dist/application/qgrid/qgrid-image-generation.js +1 -1
- package/bundle/dist/application/qgrid/qgrid-response-format.js +21 -19
- package/bundle/dist/application/qgrid/qgrid-run-lifecycle.js +40 -33
- package/bundle/dist/application/qgrid/qgrid.dispatcher.js +128 -93
- package/bundle/dist/application/qgrid/qgrid.frame.js +930 -49
- package/bundle/dist/application/qgrid/qgrid.types.js +212 -5
- package/bundle/dist/application/qgrid/schema-prompt.js +75 -48
- package/bundle/dist/application/qgrid/slack-user-map.js +1 -1
- package/bundle/dist/application/qgrid/token-death.js +1 -1
- package/bundle/dist/application/qgrid/token-subscriber.js +83 -30
- package/bundle/dist/application/qgrid/token-trigger-setup.js +1 -1
- package/bundle/dist/application/qgrid/token-window-keepalive.js +8 -6
- package/bundle/dist/application/qgrid/tool-emulation-schema.js +13 -13
- package/bundle/dist/application/qgrid/tool-emulation.js +19 -15
- package/bundle/dist/application/request-log/json-schema-type-text.js +29 -12
- package/bundle/dist/application/request-log/request-log.model.js +83 -70
- package/bundle/dist/application/request-log/request-log.types.js +1 -1
- package/bundle/dist/application/request-log/zod-code-format.js +1 -1
- package/bundle/dist/application/request-log-step/request-log-step.model.js +1 -1
- package/bundle/dist/application/request-log-step/request-log-step.types.js +1 -1
- package/bundle/dist/application/setting/setting.constant.js +1 -1
- package/bundle/dist/application/setting/setting.model.js +2 -2
- package/bundle/dist/application/setting/setting.store.js +2 -2
- package/bundle/dist/application/setting/setting.types.js +1 -1
- package/bundle/dist/application/sonamu.generated.js +32 -16
- package/bundle/dist/application/sonamu.generated.sso.js +1 -1
- package/bundle/dist/application/token/token.model.js +26 -9
- package/bundle/dist/application/token/token.types.js +20 -3
- package/bundle/dist/i18n/en.js +15 -1
- package/bundle/dist/i18n/ko.js +15 -1
- package/bundle/dist/i18n/sd.generated.js +1 -1
- package/bundle/dist/migrations/20260807133553_manual__backfill_request_log_cost.js +8 -6
- package/bundle/dist/server-bootstrap.js +4 -2
- package/bundle/dist/server-error-handler.js +1 -1
- package/bundle/dist/sonamu.config.js +37 -8
- package/bundle/dist/startup-migrations.js +17 -7
- package/bundle/dist/testing/global.js +1 -1
- package/bundle/dist/testing/schema-bench.js +0 -0
- package/bundle/dist/testing/test-database-name.js +1 -1
- package/bundle/dist/utils/providers/anthropic/anthropic-constants.js +1 -1
- package/bundle/dist/utils/providers/anthropic/anthropic-dispatcher.js +11 -5
- package/bundle/dist/utils/providers/anthropic/anthropic-quota.js +4 -4
- package/bundle/dist/utils/providers/anthropic/anthropic-thinking.js +1 -1
- package/bundle/dist/utils/providers/anthropic/claude-session.js +20 -19
- package/bundle/dist/utils/providers/anthropic/fence-strip.js +1 -1
- package/bundle/dist/utils/providers/anthropic/stream-json-adapter.js +49 -23
- package/bundle/dist/utils/providers/common/credentials.js +5 -1
- package/bundle/dist/utils/providers/common/effort.js +3 -3
- package/bundle/dist/utils/providers/common/model-cost.js +69 -14
- package/bundle/dist/utils/providers/common/provider-types.js +29 -1
- package/bundle/dist/utils/providers/common/schema-validation.js +78 -40
- package/bundle/dist/utils/providers/common/smooth-weighted-round-robin.js +1 -1
- package/bundle/dist/utils/providers/common/strictifier.js +62 -43
- package/bundle/dist/utils/providers/common/thinking.js +3 -2
- package/bundle/dist/utils/providers/common/ttft.js +1 -1
- package/bundle/dist/utils/providers/grok/grok-backend-protocol.js +427 -0
- package/bundle/dist/utils/providers/grok/grok-constants.js +55 -0
- package/bundle/dist/utils/providers/grok/grok-direct-client.js +196 -0
- package/bundle/dist/utils/providers/grok/grok-dispatcher.js +1313 -0
- package/bundle/dist/utils/providers/grok/grok-headers.js +118 -0
- package/bundle/dist/utils/providers/grok/grok-media-client.js +420 -0
- package/bundle/dist/utils/providers/grok/grok-models.js +114 -0
- package/bundle/dist/utils/providers/grok/grok-oauth.js +455 -0
- package/bundle/dist/utils/providers/grok/grok-quota.js +119 -0
- package/bundle/dist/utils/providers/grok/grok-refresh.js +192 -0
- package/bundle/dist/utils/providers/grok/grok-types.js +186 -0
- package/bundle/dist/utils/providers/grok/grok-voice-client.js +199 -0
- package/bundle/dist/utils/providers/grok/index.js +11 -0
- package/bundle/dist/utils/providers/openai/openai-backend-protocol.js +111 -83
- package/bundle/dist/utils/providers/openai/openai-callback-relay.js +3 -2
- package/bundle/dist/utils/providers/openai/openai-constants.js +1 -1
- package/bundle/dist/utils/providers/openai/openai-direct-client.js +23 -7
- package/bundle/dist/utils/providers/openai/openai-dispatcher.js +143 -106
- package/bundle/dist/utils/providers/openai/openai-image-generation.js +48 -33
- package/bundle/dist/utils/providers/openai/openai-oauth.js +28 -13
- package/bundle/dist/utils/providers/openai/openai-quota.js +38 -27
- package/bundle/dist/utils/providers/openai/openai-refresh.js +17 -9
- package/bundle/dist/utils/providers/openai/openai-sse.js +1 -1
- package/bundle/dist/utils/providers/openai/openai-transport-config.js +1 -1
- package/bundle/dist/utils/quiet-hours.js +1 -1
- package/bundle/dist/utils/slack-notify.js +14 -8
- package/bundle/dist/utils/subset-loaders.js +4 -5
- package/bundle/src/application/monit/log-buffer.ts +23 -9
- package/bundle/src/application/monit/monit.frame.ts +22 -5
- package/bundle/src/application/monit/monit.types.ts +6 -0
- package/bundle/src/application/qgrid/conv-routing.test.ts +76 -4
- package/bundle/src/application/qgrid/conv-routing.ts +35 -9
- package/bundle/src/application/qgrid/expired-token-reminder.ts +9 -1
- package/bundle/src/application/qgrid/oauth.ts +51 -15
- package/bundle/src/application/qgrid/qgrid-image-generation.ts +9 -0
- package/bundle/src/application/qgrid/qgrid-response-format.ts +50 -25
- package/bundle/src/application/qgrid/qgrid-run-lifecycle.test.ts +19 -0
- package/bundle/src/application/qgrid/qgrid-run-lifecycle.ts +84 -40
- package/bundle/src/application/qgrid/qgrid.dispatcher.test.ts +97 -0
- package/bundle/src/application/qgrid/qgrid.dispatcher.ts +274 -126
- package/bundle/src/application/qgrid/qgrid.frame.test.ts +1013 -3
- package/bundle/src/application/qgrid/qgrid.frame.ts +1415 -60
- package/bundle/src/application/qgrid/qgrid.types.ts +335 -0
- package/bundle/src/application/qgrid/schema-prompt.ts +105 -41
- package/bundle/src/application/qgrid/slack-user-map.ts +5 -0
- package/bundle/src/application/qgrid/token-death.ts +4 -0
- package/bundle/src/application/qgrid/token-subscriber.test.ts +21 -19
- package/bundle/src/application/qgrid/token-subscriber.ts +186 -53
- package/bundle/src/application/qgrid/token-trigger-setup.ts +2 -0
- package/bundle/src/application/qgrid/token-window-keepalive.ts +49 -6
- package/bundle/src/application/qgrid/tool-emulation-schema.ts +33 -29
- package/bundle/src/application/qgrid/tool-emulation.ts +52 -27
- package/bundle/src/application/request-log/json-schema-type-text.test.ts +5 -5
- package/bundle/src/application/request-log/json-schema-type-text.ts +81 -26
- package/bundle/src/application/request-log/request-log.model.test.ts +111 -4
- package/bundle/src/application/request-log/request-log.model.ts +277 -146
- package/bundle/src/application/request-log/request-log.types.ts +6 -0
- package/bundle/src/application/request-log/zod-code-format.ts +11 -0
- package/bundle/src/application/request-log-step/request-log-step.model.ts +1 -0
- package/bundle/src/application/request-log-step/request-log-step.types.ts +2 -0
- package/bundle/src/application/setting/setting.constant.ts +9 -0
- package/bundle/src/application/setting/setting.model.ts +12 -2
- package/bundle/src/application/setting/setting.store.ts +5 -1
- package/bundle/src/application/setting/setting.types.ts +8 -0
- package/bundle/src/application/sonamu.generated.http +133 -0
- package/bundle/src/application/sonamu.generated.sso.ts +2 -0
- package/bundle/src/application/sonamu.generated.ts +15 -0
- package/bundle/src/application/token/token.identity.test.ts +20 -0
- package/bundle/src/application/token/token.model.ts +66 -7
- package/bundle/src/application/token/token.types.ts +24 -1
- package/bundle/src/i18n/en.ts +19 -0
- package/bundle/src/i18n/ko.ts +21 -0
- package/bundle/src/i18n/sd.generated.ts +23 -2
- package/bundle/src/migrations/20260807133553_manual__backfill_request_log_cost.ts +16 -8
- package/bundle/src/server-bootstrap.ts +4 -2
- package/bundle/src/server-error-handler.ts +1 -0
- package/bundle/src/sonamu.config.ts +69 -7
- package/bundle/src/startup-migrations.ts +27 -9
- package/bundle/src/testing/global.ts +20 -0
- package/bundle/src/testing/schema-bench.ts +105 -30
- package/bundle/src/testing/test-database-name.ts +2 -0
- package/bundle/src/utils/providers/anthropic/anthropic-constants.ts +8 -0
- package/bundle/src/utils/providers/anthropic/anthropic-dispatcher.ts +41 -6
- package/bundle/src/utils/providers/anthropic/anthropic-quota.ts +7 -3
- package/bundle/src/utils/providers/anthropic/anthropic-thinking.ts +6 -0
- package/bundle/src/utils/providers/anthropic/claude-session.ts +58 -24
- package/bundle/src/utils/providers/anthropic/fence-strip.ts +11 -0
- package/bundle/src/utils/providers/anthropic/stream-json-adapter.ts +110 -45
- package/bundle/src/utils/providers/common/credentials.ts +8 -0
- package/bundle/src/utils/providers/common/effort.ts +9 -2
- package/bundle/src/utils/providers/common/model-cost.test.ts +48 -1
- package/bundle/src/utils/providers/common/model-cost.ts +259 -158
- package/bundle/src/utils/providers/common/provider-dispatcher.ts +98 -2
- package/bundle/src/utils/providers/common/provider-types.ts +46 -3
- package/bundle/src/utils/providers/common/schema-validation.test.ts +5 -1
- package/bundle/src/utils/providers/common/schema-validation.ts +124 -56
- package/bundle/src/utils/providers/common/smooth-weighted-round-robin.ts +4 -0
- package/bundle/src/utils/providers/common/strictifier.ts +185 -59
- package/bundle/src/utils/providers/common/thinking.ts +2 -2
- package/bundle/src/utils/providers/common/ttft.ts +4 -2
- package/bundle/src/utils/providers/grok/__fixtures__/golden-responses-1.0.60.json +164 -0
- package/bundle/src/utils/providers/grok/grok-backend-protocol.test.ts +325 -0
- package/bundle/src/utils/providers/grok/grok-backend-protocol.ts +721 -0
- package/bundle/src/utils/providers/grok/grok-constants.ts +105 -0
- package/bundle/src/utils/providers/grok/grok-direct-client.test.ts +179 -0
- package/bundle/src/utils/providers/grok/grok-direct-client.ts +313 -0
- package/bundle/src/utils/providers/grok/grok-dispatcher.test.ts +1395 -0
- package/bundle/src/utils/providers/grok/grok-dispatcher.ts +2105 -0
- package/bundle/src/utils/providers/grok/grok-headers.test.ts +149 -0
- package/bundle/src/utils/providers/grok/grok-headers.ts +199 -0
- package/bundle/src/utils/providers/grok/grok-media-client.test.ts +446 -0
- package/bundle/src/utils/providers/grok/grok-media-client.ts +703 -0
- package/bundle/src/utils/providers/grok/grok-models.test.ts +96 -0
- package/bundle/src/utils/providers/grok/grok-models.ts +161 -0
- package/bundle/src/utils/providers/grok/grok-oauth.test.ts +406 -0
- package/bundle/src/utils/providers/grok/grok-oauth.ts +771 -0
- package/bundle/src/utils/providers/grok/grok-quota.test.ts +142 -0
- package/bundle/src/utils/providers/grok/grok-quota.ts +198 -0
- package/bundle/src/utils/providers/grok/grok-refresh.test.ts +239 -0
- package/bundle/src/utils/providers/grok/grok-refresh.ts +325 -0
- package/bundle/src/utils/providers/grok/grok-types.test.ts +252 -0
- package/bundle/src/utils/providers/grok/grok-types.ts +374 -0
- package/bundle/src/utils/providers/grok/grok-voice-client.test.ts +321 -0
- package/bundle/src/utils/providers/grok/grok-voice-client.ts +284 -0
- package/bundle/src/utils/providers/grok/index.ts +20 -0
- package/bundle/src/utils/providers/openai/openai-backend-protocol.ts +221 -137
- package/bundle/src/utils/providers/openai/openai-callback-relay.ts +12 -1
- package/bundle/src/utils/providers/openai/openai-constants.ts +2 -0
- package/bundle/src/utils/providers/openai/openai-direct-client.ts +111 -21
- package/bundle/src/utils/providers/openai/openai-dispatcher.ts +322 -169
- package/bundle/src/utils/providers/openai/openai-image-generation.ts +116 -36
- package/bundle/src/utils/providers/openai/openai-oauth.ts +48 -20
- package/bundle/src/utils/providers/openai/openai-quota.ts +77 -46
- package/bundle/src/utils/providers/openai/openai-refresh.ts +42 -18
- package/bundle/src/utils/providers/openai/openai-sse.ts +27 -3
- package/bundle/src/utils/providers/openai/openai-transport-config.ts +2 -0
- package/bundle/src/utils/quiet-hours.ts +12 -1
- package/bundle/src/utils/slack-notify.ts +44 -13
- package/bundle/src/utils/subset-loaders.ts +7 -7
- package/bundle/web-dist/client/assets/clsx-5XM_pHvN.js +1 -0
- package/bundle/web-dist/client/assets/index-Be2qCZmI.css +1 -0
- package/bundle/web-dist/client/assets/index-C96h3feE.js +32 -0
- package/bundle/web-dist/client/assets/logs-DBM6U0QD.js +1 -0
- package/bundle/web-dist/client/assets/monit-BBjXa7vB.js +1 -0
- package/bundle/web-dist/client/assets/routes-Crz_PQzi.js +5 -0
- package/bundle/web-dist/client/assets/{services.generated-NP-ValHd.js → services.generated-B5lc3aIC.js} +5 -5
- package/bundle/web-dist/client/assets/settings-Cdwm3OBB.js +1 -0
- package/bundle/web-dist/client/assets/show-DBUhALT1.js +2 -0
- package/bundle/web-dist/client/assets/tokens-CVQAVJTo.js +1 -0
- package/bundle/web-dist/client/assets/total-cost-MnfKU8IN.js +1 -0
- package/bundle/web-dist/client/assets/use-oauth-login-flow-BfxYI4Oh.js +1 -0
- package/bundle/web-dist/client/assets/use-update-token-mutation-qKBC2sn7.js +1 -0
- package/bundle/web-dist/client/assets/{vendor-tanstack-DJRQAbrY.js → vendor-tanstack-DRPs09ai.js} +1 -1
- package/bundle/web-dist/client/index.html +5 -5
- package/bundle/web-dist/server/assets/clsx-D_N5PN_2.js +16 -0
- package/bundle/web-dist/server/assets/{lazyRouteComponent-CgBdabgd.js → lazyRouteComponent-AKiHfZWp.js} +129 -2
- package/bundle/web-dist/server/assets/{logs-BMyR98rU.js → logs-B7QmGJq3.js} +18 -14
- package/bundle/web-dist/server/assets/{monit-ZPt66W5M.js → monit-DFt7x3bh.js} +13 -12
- package/bundle/web-dist/server/assets/{plus-BZ9I0VgC.js → plus-DaYQRpBt.js} +1 -1
- package/bundle/web-dist/server/assets/{react-dom-Ds75e4Jl.js → react-dom-Cv23mm1K.js} +2 -129
- package/bundle/web-dist/server/assets/{refresh-cw-CztCFis-.js → refresh-cw-BpXNsAy-.js} +1 -1
- package/bundle/web-dist/server/assets/{request-model-DjGldb-h.js → request-model-eHE6YbaF.js} +1 -1
- package/bundle/web-dist/server/assets/{routes-Ct-k7nBA.js → routes-_Y-PJfgO.js} +110 -39
- package/bundle/web-dist/server/assets/{services.generated-BA4AcAf5.js → services.generated-CMJ8AHbO.js} +582 -172
- package/bundle/web-dist/server/assets/{settings-CBUZ47Xx.js → settings-JwJKxn2e.js} +14 -14
- package/bundle/web-dist/server/assets/{show-LSFIzvO2.js → show-BEv6z1v_.js} +90 -60
- package/bundle/web-dist/server/assets/{tokens-DxrBW512.js → tokens-CNT4mI-f.js} +162 -50
- package/bundle/web-dist/server/assets/total-cost-fTE9Xy82.js +18 -0
- package/bundle/web-dist/server/assets/use-oauth-login-flow-jkAkT3X6.js +2345 -0
- package/bundle/web-dist/server/assets/{use-update-token-mutation-pkArIzCP.js → use-update-token-mutation-Sm6kh02m.js} +1 -1
- package/bundle/web-dist/server/entry-server.generated.js +1644 -21655
- package/dist/cli.js +9 -9
- package/package.json +4 -11
- package/postinstall.mjs +12 -0
- package/skills/qgrid/SKILL.md +1 -0
- package/skills/qgrid/references/cli-env-and-server-boot.md +4 -1
- package/skills/qgrid/references/decision-rationale.md +23 -0
- package/skills/qgrid/references/grok-runtime.md +89 -0
- package/skills/qgrid/references/provider-runtime-differences.md +30 -29
- package/skills/qgrid/references/repo-map.md +10 -0
- package/skills/qgrid/references/token-auth-quota-lifecycle.md +56 -3
- package/bundle/dist/node_modules/.pnpm/@jridgewell_sourcemap-codec@1.5.5/node_modules/@jridgewell/sourcemap-codec/dist/sourcemap-codec.js +0 -406
- package/bundle/dist/node_modules/.pnpm/@vitest_expect@4.1.10/node_modules/@vitest/expect/dist/index.js +0 -1507
- package/bundle/dist/node_modules/.pnpm/@vitest_pretty-format@4.1.10/node_modules/@vitest/pretty-format/dist/index.js +0 -887
- package/bundle/dist/node_modules/.pnpm/@vitest_runner@4.1.10/node_modules/@vitest/runner/dist/chunk-artifact.js +0 -2756
- package/bundle/dist/node_modules/.pnpm/@vitest_runner@4.1.10/node_modules/@vitest/runner/dist/index.js +0 -8
- package/bundle/dist/node_modules/.pnpm/@vitest_runner@4.1.10/node_modules/@vitest/runner/dist/utils.js +0 -8
- package/bundle/dist/node_modules/.pnpm/@vitest_snapshot@4.1.10/node_modules/@vitest/snapshot/dist/index.js +0 -922
- package/bundle/dist/node_modules/.pnpm/@vitest_spy@4.1.10/node_modules/@vitest/spy/dist/index.js +0 -386
- package/bundle/dist/node_modules/.pnpm/@vitest_utils@4.1.10/node_modules/@vitest/utils/dist/chunk-pathe.M-eThtNZ.js +0 -121
- package/bundle/dist/node_modules/.pnpm/@vitest_utils@4.1.10/node_modules/@vitest/utils/dist/constants.js +0 -51
- package/bundle/dist/node_modules/.pnpm/@vitest_utils@4.1.10/node_modules/@vitest/utils/dist/diff.js +0 -1317
- package/bundle/dist/node_modules/.pnpm/@vitest_utils@4.1.10/node_modules/@vitest/utils/dist/display.js +0 -561
- package/bundle/dist/node_modules/.pnpm/@vitest_utils@4.1.10/node_modules/@vitest/utils/dist/error.js +0 -40
- package/bundle/dist/node_modules/.pnpm/@vitest_utils@4.1.10/node_modules/@vitest/utils/dist/helpers.js +0 -272
- package/bundle/dist/node_modules/.pnpm/@vitest_utils@4.1.10/node_modules/@vitest/utils/dist/offset.js +0 -27
- package/bundle/dist/node_modules/.pnpm/@vitest_utils@4.1.10/node_modules/@vitest/utils/dist/serialize.js +0 -77
- package/bundle/dist/node_modules/.pnpm/@vitest_utils@4.1.10/node_modules/@vitest/utils/dist/source-map.js +0 -381
- package/bundle/dist/node_modules/.pnpm/@vitest_utils@4.1.10/node_modules/@vitest/utils/dist/timers.js +0 -51
- package/bundle/dist/node_modules/.pnpm/chai@6.2.2/node_modules/chai/index.js +0 -2986
- package/bundle/dist/node_modules/.pnpm/expect-type@1.3.0/node_modules/expect-type/dist/branding.js +0 -10
- package/bundle/dist/node_modules/.pnpm/expect-type@1.3.0/node_modules/expect-type/dist/index.js +0 -109
- package/bundle/dist/node_modules/.pnpm/expect-type@1.3.0/node_modules/expect-type/dist/messages.js +0 -10
- package/bundle/dist/node_modules/.pnpm/expect-type@1.3.0/node_modules/expect-type/dist/overloads.js +0 -10
- package/bundle/dist/node_modules/.pnpm/expect-type@1.3.0/node_modules/expect-type/dist/utils.js +0 -10
- package/bundle/dist/node_modules/.pnpm/magic-string@0.30.21/node_modules/magic-string/dist/magic-string.es.js +0 -1126
- package/bundle/dist/node_modules/.pnpm/pathe@2.0.3/node_modules/pathe/dist/index.js +0 -21
- package/bundle/dist/node_modules/.pnpm/pathe@2.0.3/node_modules/pathe/dist/shared/pathe.M-eThtNZ.js +0 -432
- package/bundle/dist/node_modules/.pnpm/tinybench@2.9.0/node_modules/tinybench/dist/index.js +0 -517
- package/bundle/dist/node_modules/.pnpm/tinyrainbow@3.1.0/node_modules/tinyrainbow/dist/index.js +0 -101
- package/bundle/dist/node_modules/.pnpm/vite@8.0.3_@emnapi_core@1.11.2_@emnapi_runtime@1.11.2_@types_node@25.0.7_esbuild@0.27.4_5b35fc1fb85d2af4d105e0d74f940f50/node_modules/vite/dist/node/module-runner.js +0 -1265
- package/bundle/dist/node_modules/.pnpm/vitest@4.1.10_@opentelemetry_api@1.9.0_@types_node@25.0.7_@vitest_coverage-v8@4.1.10_vi_a1977b93af7bb69308bcb1dbab39b618/node_modules/vitest/dist/chunks/_commonjsHelpers.D26ty3Ew.js +0 -9
- package/bundle/dist/node_modules/.pnpm/vitest@4.1.10_@opentelemetry_api@1.9.0_@types_node@25.0.7_@vitest_coverage-v8@4.1.10_vi_a1977b93af7bb69308bcb1dbab39b618/node_modules/vitest/dist/chunks/benchmark.CX_oY03V.js +0 -41
- package/bundle/dist/node_modules/.pnpm/vitest@4.1.10_@opentelemetry_api@1.9.0_@types_node@25.0.7_@vitest_coverage-v8@4.1.10_vi_a1977b93af7bb69308bcb1dbab39b618/node_modules/vitest/dist/chunks/evaluatedModules.Dg1zASAC.js +0 -19
- package/bundle/dist/node_modules/.pnpm/vitest@4.1.10_@opentelemetry_api@1.9.0_@types_node@25.0.7_@vitest_coverage-v8@4.1.10_vi_a1977b93af7bb69308bcb1dbab39b618/node_modules/vitest/dist/chunks/index.Chj8NDwU.js +0 -202
- package/bundle/dist/node_modules/.pnpm/vitest@4.1.10_@opentelemetry_api@1.9.0_@types_node@25.0.7_@vitest_coverage-v8@4.1.10_vi_a1977b93af7bb69308bcb1dbab39b618/node_modules/vitest/dist/chunks/index.DdgEv5B1.js +0 -46
- package/bundle/dist/node_modules/.pnpm/vitest@4.1.10_@opentelemetry_api@1.9.0_@types_node@25.0.7_@vitest_coverage-v8@4.1.10_vi_a1977b93af7bb69308bcb1dbab39b618/node_modules/vitest/dist/chunks/rpc.MzXet3jl.js +0 -119
- package/bundle/dist/node_modules/.pnpm/vitest@4.1.10_@opentelemetry_api@1.9.0_@types_node@25.0.7_@vitest_coverage-v8@4.1.10_vi_a1977b93af7bb69308bcb1dbab39b618/node_modules/vitest/dist/chunks/test.DNmyFkvJ.js +0 -3044
- package/bundle/dist/node_modules/.pnpm/vitest@4.1.10_@opentelemetry_api@1.9.0_@types_node@25.0.7_@vitest_coverage-v8@4.1.10_vi_a1977b93af7bb69308bcb1dbab39b618/node_modules/vitest/dist/chunks/utils.BX5Fg8C4.js +0 -59
- package/bundle/dist/node_modules/.pnpm/vitest@4.1.10_@opentelemetry_api@1.9.0_@types_node@25.0.7_@vitest_coverage-v8@4.1.10_vi_a1977b93af7bb69308bcb1dbab39b618/node_modules/vitest/dist/index.js +0 -28
- package/bundle/dist/testing/setup-mocks.js +0 -16
- package/bundle/src/testing/setup-mocks.ts +0 -44
- package/bundle/web-dist/client/assets/index-Bw2s88Ot.css +0 -1
- package/bundle/web-dist/client/assets/index-DZwzUfI-.js +0 -106
- package/bundle/web-dist/client/assets/logs-Cog79DU5.js +0 -1
- package/bundle/web-dist/client/assets/monit-2auznBys.js +0 -1
- package/bundle/web-dist/client/assets/routes-B0lTeLK-.js +0 -5
- package/bundle/web-dist/client/assets/settings-D8ZxZGSJ.js +0 -1
- package/bundle/web-dist/client/assets/show-CFSERlnM.js +0 -2
- package/bundle/web-dist/client/assets/tokens-CZXUyI7O.js +0 -1
- package/bundle/web-dist/client/assets/use-oauth-login-flow-hboOs3wG.js +0 -1
- package/bundle/web-dist/client/assets/use-update-token-mutation-BQHS0byG.js +0 -1
- package/bundle/web-dist/server/assets/use-oauth-login-flow-Gn8flMK5.js +0 -178
- package/dist/cli.d.ts +0 -10
- /package/bundle/web-dist/client/assets/{cost-DH2jZbrw.js → cost-DB3HbU7T.js} +0 -0
- /package/bundle/web-dist/client/assets/{plus-B62CuqT8.js → plus-BsQdY7PS.js} +0 -0
- /package/bundle/web-dist/client/assets/{preload-helper-ca-nBW7U.js → preload-helper-DWTEM3RW.js} +0 -0
- /package/bundle/web-dist/client/assets/{refresh-cw-NNcU0Cmq.js → refresh-cw-D9IUmT23.js} +0 -0
- /package/bundle/web-dist/client/assets/{request-model-CojtinqK.js → request-model-Bpp_rkK8.js} +0 -0
- /package/bundle/web-dist/server/assets/{cost-eEHeTRT5.js → cost-BC9yzyA9.js} +0 -0
|
@@ -7,10 +7,11 @@
|
|
|
7
7
|
* - rotation 감지: 새 refresh_token 있으면 DB 즉시 업데이트
|
|
8
8
|
*/
|
|
9
9
|
import { getLogger } from "@logtape/logtape";
|
|
10
|
+
import { z } from "zod";
|
|
10
11
|
|
|
11
12
|
import { deactivateAuthDeadToken } from "../../../application/qgrid/token-death";
|
|
12
13
|
import { TokenModel } from "../../../application/token/token.model";
|
|
13
|
-
import {
|
|
14
|
+
import { OpenAICredentials } from "../../../application/token/token.types";
|
|
14
15
|
|
|
15
16
|
const logger = getLogger(["qgrid", "openai-refresh"]);
|
|
16
17
|
|
|
@@ -23,6 +24,26 @@ const PERMANENT_FAILURE_CODES = new Set([
|
|
|
23
24
|
"refresh_token_invalidated",
|
|
24
25
|
]);
|
|
25
26
|
|
|
27
|
+
// Only short snake_case codes are trusted; anything else in a 401 body reads as "unknown".
|
|
28
|
+
const refreshErrorCodeSchema = z.string().regex(/^[a-z_]{1,80}$/);
|
|
29
|
+
|
|
30
|
+
// `error` is either the code itself or an object carrying `code`.
|
|
31
|
+
const refreshErrorBodySchema = z.looseObject({
|
|
32
|
+
error: z
|
|
33
|
+
.union([
|
|
34
|
+
refreshErrorCodeSchema,
|
|
35
|
+
z.looseObject({ code: refreshErrorCodeSchema }).transform((error) => error.code),
|
|
36
|
+
])
|
|
37
|
+
.optional(),
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
const refreshResponseSchema = z.object({
|
|
41
|
+
access_token: z.string(),
|
|
42
|
+
refresh_token: z.string().nullish(),
|
|
43
|
+
expires_in: z.number().nullish(),
|
|
44
|
+
id_token: z.string().nullish(),
|
|
45
|
+
});
|
|
46
|
+
|
|
26
47
|
// per-token inflight promise dedup + minimum interval
|
|
27
48
|
const inflightRefresh = new Map<number, Promise<RefreshResult>>();
|
|
28
49
|
const lastRefreshTime = new Map<number, number>();
|
|
@@ -37,14 +58,18 @@ interface RefreshResult {
|
|
|
37
58
|
export async function handleChatgptAuthTokensRefresh(tokenId: number): Promise<RefreshResult> {
|
|
38
59
|
logger.info(`refresh requested for token ${tokenId} (pid=${process.pid})`);
|
|
39
60
|
const existing = inflightRefresh.get(tokenId);
|
|
61
|
+
|
|
40
62
|
if (existing) {
|
|
41
63
|
logger.info(`refresh dedup for token ${tokenId}`);
|
|
64
|
+
|
|
42
65
|
return existing;
|
|
43
66
|
}
|
|
44
67
|
|
|
45
68
|
const lastAt = lastRefreshTime.get(tokenId) ?? 0;
|
|
69
|
+
|
|
46
70
|
if (Date.now() - lastAt < REFRESH_MIN_INTERVAL_MS) {
|
|
47
71
|
logger.info(`refresh too soon for token ${tokenId}, reading current creds from DB`);
|
|
72
|
+
|
|
48
73
|
return readCurrentCreds(tokenId);
|
|
49
74
|
}
|
|
50
75
|
|
|
@@ -61,8 +86,10 @@ export async function handleChatgptAuthTokensRefresh(tokenId: number): Promise<R
|
|
|
61
86
|
|
|
62
87
|
async function readCurrentCreds(tokenId: number): Promise<RefreshResult> {
|
|
63
88
|
const token = await TokenModel.findOne("A", { id: tokenId });
|
|
89
|
+
|
|
64
90
|
if (!token || token.provider !== "openai") throw new Error(`token ${tokenId} not found`);
|
|
65
|
-
const creds = token.credentials
|
|
91
|
+
const creds = OpenAICredentials.parse(token.credentials);
|
|
92
|
+
|
|
66
93
|
return {
|
|
67
94
|
accessToken: creds.accessToken,
|
|
68
95
|
chatgptAccountId: creds.accountId,
|
|
@@ -72,11 +99,13 @@ async function readCurrentCreds(tokenId: number): Promise<RefreshResult> {
|
|
|
72
99
|
|
|
73
100
|
async function doRefresh(tokenId: number): Promise<RefreshResult> {
|
|
74
101
|
const token = await TokenModel.findOne("A", { id: tokenId });
|
|
102
|
+
|
|
75
103
|
if (!token || token.provider !== "openai") {
|
|
76
104
|
throw new Error(`token ${tokenId} not found or not openai`);
|
|
77
105
|
}
|
|
78
106
|
|
|
79
|
-
const creds = token.credentials
|
|
107
|
+
const creds = OpenAICredentials.loose().parse(token.credentials);
|
|
108
|
+
|
|
80
109
|
if (!creds.refreshToken) {
|
|
81
110
|
throw new Error(`token ${tokenId} has no refreshToken`);
|
|
82
111
|
}
|
|
@@ -103,20 +132,16 @@ async function doRefresh(tokenId: number): Promise<RefreshResult> {
|
|
|
103
132
|
// refresh_token_invalidated → 서버측 revoke
|
|
104
133
|
// 위 3개는 모두 영구 실패. 재등록 외 복구 불가.
|
|
105
134
|
let errorCode: string | undefined;
|
|
135
|
+
|
|
106
136
|
try {
|
|
107
|
-
|
|
108
|
-
const code =
|
|
109
|
-
typeof error === "string"
|
|
110
|
-
? error
|
|
111
|
-
: error && typeof error === "object" && "code" in error
|
|
112
|
-
? error.code
|
|
113
|
-
: undefined;
|
|
114
|
-
if (typeof code === "string" && /^[a-z_]{1,80}$/.test(code)) errorCode = code;
|
|
137
|
+
errorCode = refreshErrorBodySchema.safeParse(JSON.parse(body)).data?.error;
|
|
115
138
|
} catch {}
|
|
139
|
+
|
|
116
140
|
logger.error(
|
|
117
141
|
`OpenAI refresh FAILED token=${token.name}(id=${tokenId}) status=${resp.status} ` +
|
|
118
142
|
`code=${errorCode ?? "unknown"}`,
|
|
119
143
|
);
|
|
144
|
+
|
|
120
145
|
// 영구 실패는 errorCode 로만 판정한다 — status 를 함께 요구하면 provider 가
|
|
121
146
|
// 상태코드를 바꿀 때 판정이 조용히 멎는다.
|
|
122
147
|
if (errorCode && PERMANENT_FAILURE_CODES.has(errorCode)) {
|
|
@@ -125,21 +150,19 @@ async function doRefresh(tokenId: number): Promise<RefreshResult> {
|
|
|
125
150
|
`openai:${errorCode}`,
|
|
126
151
|
);
|
|
127
152
|
}
|
|
153
|
+
|
|
128
154
|
throw new Error(`OpenAI refresh failed: ${resp.status} ${errorCode ?? "unknown"}`);
|
|
129
155
|
}
|
|
130
156
|
|
|
131
|
-
const data = (await resp.json())
|
|
132
|
-
access_token: string;
|
|
133
|
-
refresh_token?: string;
|
|
134
|
-
expires_in?: number;
|
|
135
|
-
id_token?: string;
|
|
136
|
-
};
|
|
157
|
+
const data = refreshResponseSchema.parse(await resp.json());
|
|
137
158
|
|
|
138
159
|
const newRefreshToken = data.refresh_token ?? creds.refreshToken;
|
|
160
|
+
|
|
139
161
|
const rotated =
|
|
140
162
|
data.refresh_token !== null &&
|
|
141
163
|
data.refresh_token !== undefined &&
|
|
142
164
|
data.refresh_token !== creds.refreshToken;
|
|
165
|
+
|
|
143
166
|
if (rotated) {
|
|
144
167
|
logger.info(`refresh_token rotated for token ${token.name}`);
|
|
145
168
|
}
|
|
@@ -151,9 +174,10 @@ async function doRefresh(tokenId: number): Promise<RefreshResult> {
|
|
|
151
174
|
accessTokenExpiresAt: data.expires_in
|
|
152
175
|
? Date.now() + data.expires_in * 1000
|
|
153
176
|
: creds.accessTokenExpiresAt,
|
|
154
|
-
...(data.id_token ? { idToken: data.id_token } : {}),
|
|
155
177
|
};
|
|
156
178
|
|
|
179
|
+
if (data.id_token) updatedCreds.idToken = data.id_token;
|
|
180
|
+
|
|
157
181
|
// 회전(rotated)이 일어났는데 이 save 가 실패하면, OpenAI 쪽 옛 refresh_token 은 이미
|
|
158
182
|
// 폐기된 상태에서 DB 에는 옛 토큰이 남는다 → 다음 refresh 가 refresh_token_reused 로
|
|
159
183
|
// 영구 사망. 이 구간 실패는 토큰 사망의 직접 원인이므로 반드시 크게 남긴다.
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import {
|
|
2
|
+
type JsonValue,
|
|
2
3
|
normalizeOpenAIEvent,
|
|
3
4
|
OpenAIProtocolError,
|
|
4
5
|
type OpenAINormalizedEvent,
|
|
@@ -6,46 +7,61 @@ import {
|
|
|
6
7
|
|
|
7
8
|
export interface OpenAIEventStream extends AsyncIterable<OpenAINormalizedEvent> {}
|
|
8
9
|
|
|
9
|
-
function parseBlock(block: string):
|
|
10
|
+
function parseBlock(block: string): JsonValue | undefined {
|
|
10
11
|
const data: string[] = [];
|
|
12
|
+
|
|
11
13
|
for (const line of block.replaceAll("\r\n", "\n").replaceAll("\r", "\n").split("\n")) {
|
|
12
14
|
if (line.startsWith(":")) continue;
|
|
13
15
|
const separator = line.indexOf(":");
|
|
14
16
|
const field = separator < 0 ? line : line.slice(0, separator);
|
|
15
17
|
let value = separator < 0 ? "" : line.slice(separator + 1);
|
|
18
|
+
|
|
16
19
|
if (value.startsWith(" ")) value = value.slice(1);
|
|
20
|
+
|
|
17
21
|
if (field === "data") data.push(value);
|
|
18
22
|
}
|
|
23
|
+
|
|
19
24
|
if (!data.length) return undefined;
|
|
20
25
|
const payload = data.join("\n");
|
|
26
|
+
|
|
21
27
|
if (payload === "[DONE]") return payload;
|
|
28
|
+
|
|
22
29
|
try {
|
|
23
|
-
|
|
30
|
+
// JSON.parse without a reviver yields only JSON values.
|
|
31
|
+
const parsed: JsonValue = JSON.parse(payload);
|
|
32
|
+
|
|
33
|
+
return parsed;
|
|
24
34
|
} catch (error) {
|
|
25
35
|
throw new OpenAIProtocolError(`Invalid OpenAI SSE JSON: ${String(error)}`);
|
|
26
36
|
}
|
|
27
37
|
}
|
|
28
38
|
|
|
29
39
|
/** Parses arbitrary UTF-8/chunk boundaries and all complete SSE event blocks. */
|
|
30
|
-
export async function* parseOpenAISSE(body: ReadableStream<Uint8Array>): AsyncGenerator<
|
|
40
|
+
export async function* parseOpenAISSE(body: ReadableStream<Uint8Array>): AsyncGenerator<JsonValue> {
|
|
31
41
|
const reader = body.getReader();
|
|
32
42
|
const decoder = new TextDecoder();
|
|
33
43
|
let buffer = "";
|
|
44
|
+
|
|
34
45
|
try {
|
|
35
46
|
while (true) {
|
|
36
47
|
const { done, value } = await reader.read();
|
|
37
48
|
buffer += decoder.decode(value, { stream: !done });
|
|
38
49
|
let match: RegExpExecArray | null;
|
|
50
|
+
|
|
39
51
|
while ((match = /\r\n\r\n|\n\n|\r\r/.exec(buffer))) {
|
|
40
52
|
const block = buffer.slice(0, match.index);
|
|
41
53
|
buffer = buffer.slice(match.index + match[0].length);
|
|
42
54
|
const parsed = parseBlock(block);
|
|
55
|
+
|
|
43
56
|
if (parsed !== undefined) yield parsed;
|
|
44
57
|
}
|
|
58
|
+
|
|
45
59
|
if (done) break;
|
|
46
60
|
}
|
|
61
|
+
|
|
47
62
|
if (buffer.trim()) {
|
|
48
63
|
const parsed = parseBlock(buffer);
|
|
64
|
+
|
|
49
65
|
if (parsed !== undefined) yield parsed;
|
|
50
66
|
}
|
|
51
67
|
} finally {
|
|
@@ -58,22 +74,30 @@ export async function* normalizeOpenAISSE(
|
|
|
58
74
|
body: ReadableStream<Uint8Array>,
|
|
59
75
|
): AsyncGenerator<OpenAINormalizedEvent> {
|
|
60
76
|
let terminal = false;
|
|
77
|
+
|
|
61
78
|
for await (const raw of parseOpenAISSE(body)) {
|
|
62
79
|
if (raw === "[DONE]") {
|
|
63
80
|
if (!terminal) throw new OpenAIProtocolError("OpenAI stream ended before response.completed");
|
|
81
|
+
|
|
64
82
|
return;
|
|
65
83
|
}
|
|
84
|
+
|
|
66
85
|
if (terminal) throw new OpenAIProtocolError("OpenAI stream emitted an event after termination");
|
|
67
86
|
const event = normalizeOpenAIEvent(raw);
|
|
87
|
+
|
|
68
88
|
if (!event) continue;
|
|
89
|
+
|
|
69
90
|
if (event.type === "error") {
|
|
70
91
|
terminal = true;
|
|
71
92
|
yield event;
|
|
93
|
+
|
|
72
94
|
return;
|
|
73
95
|
}
|
|
96
|
+
|
|
74
97
|
if (event.type === "completed") terminal = true;
|
|
75
98
|
yield event;
|
|
76
99
|
}
|
|
100
|
+
|
|
77
101
|
if (!terminal)
|
|
78
102
|
throw new OpenAIProtocolError("OpenAI SSE connection closed before a terminal event");
|
|
79
103
|
}
|
|
@@ -8,10 +8,12 @@ export function resolveOpenAITransportKind(
|
|
|
8
8
|
env: Record<string, string | undefined> = process.env,
|
|
9
9
|
): OpenAITransportKind {
|
|
10
10
|
const transport = env.QGRID_OPENAI_TRANSPORT ?? "websocket";
|
|
11
|
+
|
|
11
12
|
if (transport !== "https" && transport !== "websocket") {
|
|
12
13
|
throw new Error(
|
|
13
14
|
`Invalid QGRID_OPENAI_TRANSPORT value: ${transport}. Expected https or websocket.`,
|
|
14
15
|
);
|
|
15
16
|
}
|
|
17
|
+
|
|
16
18
|
return transport;
|
|
17
19
|
}
|
|
@@ -35,10 +35,16 @@ const SEOUL_FORMATTER = new Intl.DateTimeFormat("en-US", {
|
|
|
35
35
|
weekday: "short",
|
|
36
36
|
});
|
|
37
37
|
|
|
38
|
-
|
|
38
|
+
interface SeoulTimeParts {
|
|
39
|
+
hour: number;
|
|
40
|
+
weekday: string;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function seoulParts(at: Date): SeoulTimeParts {
|
|
39
44
|
const parts = SEOUL_FORMATTER.formatToParts(at);
|
|
40
45
|
const hour = Number(parts.find((p) => p.type === "hour")?.value ?? "0");
|
|
41
46
|
const weekday = parts.find((p) => p.type === "weekday")?.value ?? "";
|
|
47
|
+
|
|
42
48
|
// Intl 은 자정을 24 로 줄 수 있다. 0..23 으로 맞춘다.
|
|
43
49
|
return { hour: hour % 24, weekday };
|
|
44
50
|
}
|
|
@@ -46,7 +52,9 @@ function seoulParts(at: Date): { hour: number; weekday: string } {
|
|
|
46
52
|
/** 0..23 범위를 벗어나거나 숫자가 아니면 기본값으로 떨어진다. */
|
|
47
53
|
function boundedHour(raw: string | undefined, fallback: number): number {
|
|
48
54
|
const parsed = Number(raw);
|
|
55
|
+
|
|
49
56
|
if (!Number.isInteger(parsed) || parsed < 0 || parsed > 23) return fallback;
|
|
57
|
+
|
|
50
58
|
return parsed;
|
|
51
59
|
}
|
|
52
60
|
|
|
@@ -60,6 +68,7 @@ export function isQuietHours(at: Date, readSetting: ReadSetting): boolean {
|
|
|
60
68
|
const { hour, weekday } = seoulParts(at);
|
|
61
69
|
|
|
62
70
|
const isWeekend = weekday === "Sat" || weekday === "Sun";
|
|
71
|
+
|
|
63
72
|
if (isWeekend && readSetting("slack.notifyOnWeekends", "SLACK_NOTIFY_ON_WEEKENDS") !== "true") {
|
|
64
73
|
return true;
|
|
65
74
|
}
|
|
@@ -68,6 +77,7 @@ export function isQuietHours(at: Date, readSetting: ReadSetting): boolean {
|
|
|
68
77
|
readSetting("slack.quietFromHour", "SLACK_QUIET_FROM_HOUR"),
|
|
69
78
|
DEFAULT_QUIET_FROM_HOUR,
|
|
70
79
|
);
|
|
80
|
+
|
|
71
81
|
const until = boundedHour(
|
|
72
82
|
readSetting("slack.quietUntilHour", "SLACK_QUIET_UNTIL_HOUR"),
|
|
73
83
|
DEFAULT_QUIET_UNTIL_HOUR,
|
|
@@ -75,5 +85,6 @@ export function isQuietHours(at: Date, readSetting: ReadSetting): boolean {
|
|
|
75
85
|
|
|
76
86
|
// 시작과 종료가 같으면 구간이 비어 있다는 뜻으로 읽어 조용 시간을 두지 않는다.
|
|
77
87
|
if (from === until) return false;
|
|
88
|
+
|
|
78
89
|
return from > until ? hour >= from || hour < until : hour >= from && hour < until;
|
|
79
90
|
}
|
|
@@ -6,10 +6,30 @@
|
|
|
6
6
|
* 알림 경로가 토큰 라우팅이나 오류 전파를 지연·차단해서는 안 된다.
|
|
7
7
|
*/
|
|
8
8
|
import { getLogger } from "@logtape/logtape";
|
|
9
|
+
import { z } from "zod";
|
|
9
10
|
|
|
10
11
|
import { getSetting } from "../application/setting/setting.store";
|
|
11
12
|
import { isQuietHours } from "./quiet-hours";
|
|
12
13
|
|
|
14
|
+
type SlackTextObject = { type: "mrkdwn"; text: string };
|
|
15
|
+
|
|
16
|
+
type SlackBlock =
|
|
17
|
+
| { type: "section"; text: SlackTextObject }
|
|
18
|
+
| { type: "context"; elements: SlackTextObject[] };
|
|
19
|
+
|
|
20
|
+
interface SlackAttachment {
|
|
21
|
+
fallback: string;
|
|
22
|
+
color?: string;
|
|
23
|
+
blocks: SlackBlock[];
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// chat.postMessage answers HTTP 200 with ok:false for most configuration errors. A malformed
|
|
27
|
+
// error field must not hide ok:true, so it degrades to undefined instead of failing the parse.
|
|
28
|
+
const slackPostMessageResponseSchema = z.looseObject({
|
|
29
|
+
ok: z.boolean().optional(),
|
|
30
|
+
error: z.string().optional().catch(undefined),
|
|
31
|
+
});
|
|
32
|
+
|
|
13
33
|
const logger = getLogger(["qgrid", "slack"]);
|
|
14
34
|
|
|
15
35
|
const SLACK_POST_MESSAGE_URL = "https://slack.com/api/chat.postMessage";
|
|
@@ -49,11 +69,14 @@ export async function notifySlack(
|
|
|
49
69
|
const botToken = readSetting("slack.botToken", "SLACK_BOT_TOKEN");
|
|
50
70
|
const channel = readSetting("slack.channelId", "SLACK_CHANNEL_ID");
|
|
51
71
|
const { title, subject, context, color, urgent, now } = notification;
|
|
72
|
+
|
|
52
73
|
if (!botToken || !channel) {
|
|
53
74
|
logger.debug(`slack not configured, skipping notification: ${title} ${subject ?? ""}`);
|
|
75
|
+
|
|
54
76
|
if (notification.throwOnFailure) {
|
|
55
77
|
throw new SlackNotificationError("Slack 봇 토큰과 채널 ID를 설정해 주세요");
|
|
56
78
|
}
|
|
79
|
+
|
|
57
80
|
return;
|
|
58
81
|
}
|
|
59
82
|
|
|
@@ -61,26 +84,39 @@ export async function notifySlack(
|
|
|
61
84
|
// 끈 상태로 provider 가 전부 죽으면 연휴 내내 서비스가 멈춘 줄 아무도 모른다.
|
|
62
85
|
if (!urgent && readSetting("slack.enabled", "SLACK_ENABLED") === "false") {
|
|
63
86
|
logger.debug(`slack disabled, skipping notification: ${title} ${subject ?? ""}`);
|
|
87
|
+
|
|
64
88
|
return;
|
|
65
89
|
}
|
|
66
90
|
|
|
67
91
|
// 만료 알림은 주기적으로 반복되므로 여기서 버려도 다음 근무 시간 첫 주기에 다시 온다.
|
|
68
92
|
if (!urgent && isQuietHours(now ?? new Date(), readSetting)) {
|
|
69
93
|
logger.debug(`quiet hours, skipping notification: ${title} ${subject ?? ""}`);
|
|
94
|
+
|
|
70
95
|
return;
|
|
71
96
|
}
|
|
72
97
|
|
|
73
|
-
const blocks:
|
|
98
|
+
const blocks: SlackBlock[] = [
|
|
74
99
|
{
|
|
75
100
|
type: "section",
|
|
76
101
|
text: { type: "mrkdwn", text: subject ? `*${title}* \`${subject}\`` : `*${title}*` },
|
|
77
102
|
},
|
|
78
103
|
];
|
|
104
|
+
|
|
79
105
|
if (context) {
|
|
80
106
|
blocks.push({ type: "context", elements: [{ type: "mrkdwn", text: context }] });
|
|
81
107
|
}
|
|
82
108
|
|
|
83
109
|
try {
|
|
110
|
+
// fallback 은 attachment 안에 둔다. 최상위 text 로 보내면 blocks 가 정상 렌더되는
|
|
111
|
+
// 화면에서도 함께 출력돼 제목이 두 번 보인다. attachment fallback 은 blocks 를
|
|
112
|
+
// 못 그리는 클라이언트와 푸시 알림에서만 쓰인다.
|
|
113
|
+
const attachment: SlackAttachment = {
|
|
114
|
+
fallback: subject ? `${title} — ${subject}` : title,
|
|
115
|
+
blocks,
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
if (color) attachment.color = color;
|
|
119
|
+
|
|
84
120
|
const res = await fetch(SLACK_POST_MESSAGE_URL, {
|
|
85
121
|
method: "POST",
|
|
86
122
|
headers: {
|
|
@@ -89,28 +125,21 @@ export async function notifySlack(
|
|
|
89
125
|
},
|
|
90
126
|
body: JSON.stringify({
|
|
91
127
|
channel,
|
|
92
|
-
|
|
93
|
-
// 화면에서도 함께 출력돼 제목이 두 번 보인다. attachment fallback 은 blocks 를
|
|
94
|
-
// 못 그리는 클라이언트와 푸시 알림에서만 쓰인다.
|
|
95
|
-
attachments: [
|
|
96
|
-
{
|
|
97
|
-
fallback: subject ? `${title} — ${subject}` : title,
|
|
98
|
-
...(color ? { color } : {}),
|
|
99
|
-
blocks,
|
|
100
|
-
},
|
|
101
|
-
],
|
|
128
|
+
attachments: [attachment],
|
|
102
129
|
}),
|
|
103
130
|
signal: AbortSignal.timeout(SLACK_TIMEOUT_MS),
|
|
104
131
|
});
|
|
105
132
|
|
|
106
133
|
// bot 미초대(not_in_channel)·잘못된 채널 등 가장 흔한 오설정은 HTTP 200 + ok:false 로 온다.
|
|
107
134
|
// status 만 보면 조용히 성공 처리돼 알림이 통째로 사라진다.
|
|
108
|
-
const body = (await res.json().catch(() => null))
|
|
135
|
+
const body = slackPostMessageResponseSchema.safeParse(await res.json().catch(() => null)).data;
|
|
136
|
+
|
|
109
137
|
if (!res.ok || body?.ok !== true) {
|
|
110
138
|
const code =
|
|
111
|
-
|
|
139
|
+
body?.error !== undefined && /^[a-z_]{1,80}$/.test(body.error)
|
|
112
140
|
? body.error
|
|
113
141
|
: `HTTP ${res.status}`;
|
|
142
|
+
|
|
114
143
|
throw new SlackNotificationError(`Slack 알림 발송 실패: ${code}`);
|
|
115
144
|
}
|
|
116
145
|
} catch (e) {
|
|
@@ -118,7 +147,9 @@ export async function notifySlack(
|
|
|
118
147
|
e instanceof SlackNotificationError
|
|
119
148
|
? e
|
|
120
149
|
: new SlackNotificationError("Slack 알림 발송 실패: 네트워크 오류 또는 응답 시간 초과");
|
|
150
|
+
|
|
121
151
|
logger.warn(error.message);
|
|
152
|
+
|
|
122
153
|
if (notification.throwOnFailure) throw error;
|
|
123
154
|
}
|
|
124
155
|
}
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { type SubsetQuery } from "sonamu";
|
|
2
2
|
|
|
3
3
|
export function getSubsetLoaders(subsets: string[], subsetQueries: Record<string, SubsetQuery>) {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
4
|
+
const loaders: Record<string, SubsetQuery["loaders"]> = {};
|
|
5
|
+
|
|
6
|
+
for (const subset of subsets) {
|
|
7
|
+
loaders[subset] = subsetQueries[subset]?.loaders ?? [];
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
return loaders;
|
|
11
11
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
function e(t){var n,r,i=``;if(typeof t==`string`||typeof t==`number`)i+=t;else if(typeof t==`object`)if(Array.isArray(t)){var a=t.length;for(n=0;n<a;n++)t[n]&&(r=e(t[n]))&&(i&&(i+=` `),i+=r)}else for(r in t)t[r]&&(i&&(i+=` `),i+=r);return i}function t(){for(var t,n,r=0,i=``,a=arguments.length;r<a;r++)(t=arguments[r])&&(n=e(t))&&(i&&(i+=` `),i+=n);return i}export{t};
|