diff --git a/.github/workflows/sync-upstream.yml b/.github/workflows/sync-upstream.yml new file mode 100644 index 0000000000..befc697f8c --- /dev/null +++ b/.github/workflows/sync-upstream.yml @@ -0,0 +1,17 @@ +name: Sync upstream + +on: + schedule: + - cron: '0 6 * * *' # daily at 6 AM UTC + workflow_dispatch: # manual trigger from Actions tab + +jobs: + sync: + runs-on: ubuntu-latest + steps: + - name: Sync fork via GitHub API + run: | + gh api repos/${{ github.repository }}/merge-upstream \ + -f branch=main + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/scripts/notification-relay.cjs b/scripts/notification-relay.cjs index 7a6dbe8783..4bd7fb1d40 100644 --- a/scripts/notification-relay.cjs +++ b/scripts/notification-relay.cjs @@ -75,7 +75,7 @@ async function deactivateChannel(userId, channelType) { // ── Private IP guard ───────────────────────────────────────────────────────── function isPrivateIP(ip) { - return /^(10\.|172\.(1[6-9]|2\d|3[01])\.|192\.168\.|127\.|::1|fc|fd)/.test(ip); + return /^(0\.|10\.|172\.(1[6-9]|2\d|3[01])\.|192\.168\.|169\.254\.|127\.|::1$|fe80:|fc|fd)/.test(ip); } // ── Delivery: Telegram ──────────────────────────────────────────────────────── diff --git a/src/components/ChatAnalystPanel.ts b/src/components/ChatAnalystPanel.ts index 4c280d6faa..8263224d3b 100644 --- a/src/components/ChatAnalystPanel.ts +++ b/src/components/ChatAnalystPanel.ts @@ -67,6 +67,7 @@ export class ChatAnalystPanel extends Panel { private domainFocus = 'all'; private streamAbort: AbortController | null = null; private isStreaming = false; + private scrollRafId = 0; private messagesEl!: HTMLElement; private inputEl: HTMLTextAreaElement | null = null; @@ -265,7 +266,9 @@ export class ChatAnalystPanel extends Panel { } private scrollToBottom(): void { - requestAnimationFrame(() => { + if (this.scrollRafId) return; + this.scrollRafId = requestAnimationFrame(() => { + this.scrollRafId = 0; this.messagesEl.scrollTop = this.messagesEl.scrollHeight; }); } @@ -448,6 +451,7 @@ export class ChatAnalystPanel extends Panel { override destroy(): void { this.streamAbort?.abort(); this.streamAbort = null; + cancelAnimationFrame(this.scrollRafId); super.destroy(); } } diff --git a/src/services/cross-module-integration.ts b/src/services/cross-module-integration.ts index d6ab173425..fc9a84662b 100644 --- a/src/services/cross-module-integration.ts +++ b/src/services/cross-module-integration.ts @@ -771,7 +771,7 @@ function identifyTopRisks( } const leadSanctions = sanctions?.countries[0]; - if (leadSanctions && (sanctions.newEntryCount > 0 || leadSanctions.entryCount >= 25)) { + if (leadSanctions && sanctions && (sanctions.newEntryCount > 0 || leadSanctions.entryCount >= 25)) { const label = sanctions.newEntryCount > 0 ? 'Sanctions burst' : 'Sanctions pressure'; risks.push(`${label}: ${leadSanctions.countryName} (${leadSanctions.entryCount}, +${leadSanctions.newEntryCount} new)`); } diff --git a/src/services/market-watchlist.ts b/src/services/market-watchlist.ts index 22abc522da..43f64e0274 100644 --- a/src/services/market-watchlist.ts +++ b/src/services/market-watchlist.ts @@ -39,7 +39,7 @@ function coerceEntry(v: unknown): MarketWatchlistEntry | null { return { symbol: sym }; } if (v && typeof v === 'object') { - const obj = v as any; + const obj = v as Record; const sym = normalizeSymbol(String(obj.symbol || '')); if (!sym) return null; const name = normalizeName(typeof obj.name === 'string' ? obj.name : undefined);